Wie schreibe ich ein Skript, das Eingaben aus einer Datei oder aus stdin akzeptiert?

93289
gilad hoch

Wie kann man ein Skript schreiben, das Eingaben entweder aus einem Dateinamenargument oder aus stdin akzeptiert?

Zum Beispiel könnten Sie lessdiesen Weg verwenden. man kann es ausführen less filenameund gleichwertig cat filename | less.

Gibt es eine einfache "out of the box" Möglichkeit, dies zu tun? oder muss ich das Rad neu erfinden und ein bisschen Logik in das Skript schreiben?

49
@PlasmaPower Solange sich die Frage zu SU auf einem bestimmten Thema befindet, ist es nicht erforderlich, * auf einem anderen SE-Standort zu fragen. Viele SE-Standorte überschneiden sich; Im Allgemeinen brauchen wir keine überlappende Website vorzuschlagen, es sei denn, die Frage ist entweder off-topic (in diesem Fall, wählen Sie für die Migration) oder thematisch, ohne dass Sie eine große Antwort erhalten (in diesem Fall sollte der Fragesteller für Moderator- Aufmerksamkeit / Migration, nicht Cross-Post). Bob vor 9 Jahren 0
Related: [Wie kann ich aus einer Datei oder von einem Standard in bash lesen?] (Http://stackoverflow.com/q/6980090/55075) bei SO kenorb vor 8 Jahren 0

8 Antworten auf die Frage

52
suspectus

Wenn das Dateiargument das erste Argument für Ihr Skript ist, prüfen Sie, ob es ein Argument ( $1) gibt und dass es sich um eine Datei handelt. Sonst Leseeingang von stdin -

So könnte Ihr Skript so etwas enthalten:

#!/bin/bash [ $# -ge 1 -a -f "$1" ] && input="$1" || input="-" cat $input 

zb dann kannst du das script gerne aufrufen

./myscript.sh filename 

oder

who | ./myscript.sh 

Einige Erklärungen zum Skript bearbeiten :

[ $# -ge 1 -a -f "$1" ]- Wenn mindestens ein Befehlszeilenargument ( $# -ge 1) AND (-a-Operator) das erste Argument eine Datei ist (-f prüft, ob "$ 1" eine Datei ist), ist das Testergebnis wahr.

&&ist der logische UND-Operator der Shell. Wenn der Test wahr ist, dann zuweisen input="$1"und cat $inputausgeben wird die Datei.

||ist der logische OR-Operator der Shell. Wenn der Test falsch ist, werden die folgenden Befehle ||analysiert. Eingang ist "-" zugeordnet. cat -liest von der Tastatur.

Zusammenfassend: Wenn das Skriptargument angegeben wird und es sich um eine Datei handelt, wird die Variableneingabe dem Dateinamen zugewiesen. Wenn kein gültiges Argument vorhanden ist, liest cat von der Tastatur.

was bedeutet `&& input =" $ 1 "|| input = "-" `do und warum ist es außerhalb des` test'-Operators? cmo vor 8 Jahren 0
Ich habe einen Schnitt mit Erklärungen hinzugefügt, von dem ich hoffe, dass er helfen wird. suspectus vor 8 Jahren 0
Was ist, wenn das Skript mehrere Argumente hat (`$ @`)? g33kz0r vor 5 Jahren 0
12
Lukasz Daniluk

readliest von der Standardeingabe. Das Umleiten von file ( ./script <someinput) oder through pipe ( dosomething | ./script) führt nicht dazu, dass es anders funktioniert.

Alles, was Sie tun müssen, ist, alle Zeilen der Eingabe durchzugehen (und es unterscheidet sich nicht von der Iteration über die Zeilen in der Datei).

(Beispielcode, verarbeitet nur eine Zeile)

#!/bin/bash  read var echo $var 

Gibt die erste Zeile Ihrer Standardeingabe wieder (entweder durch <oder |).

Vielen Dank! Ich wähle die andere Antwort, weil sie mir besser passte. Ich habe ein anderes Skript geschrieben, und ich wollte keine Schleife machen, bis alle Eingaben empfangen wurden (dies könnte eine Menge Eingaben sein ... wäre verschwenderisch). gilad hoch vor 9 Jahren 0
4
Jacob Hayes

Sie erwähnen nicht, welche Shell Sie verwenden möchten, also gehe ich von bash aus, obwohl dies ziemlich üblich ist.

Dateiargumente

Auf Argumente kann über die Variablen zugegriffen werden $1- $n( $0gibt den Befehl zurück, mit dem das Programm ausgeführt wird). Angenommen, ich habe ein Skript, das nur cateine Anzahl von Dateien mit einem Trennzeichen zwischen ihnen aussieht:

#!/usr/bin/env bash # # Parameters: # 1: string delimiter between arguments 2-n # 2-n: file(s) to cat out for arg in ${@:2} # $@ is the array of arguments, ${@:2} slices it starting at 2. do cat $arg echo $1 done 

In diesem Fall übergeben wir einen Dateinamen an cat. Wenn Sie die Daten in der Datei umwandeln möchten (ohne sie explizit zu schreiben und umzuschreiben), können Sie den Dateiinhalt auch in einer Variablen speichern:

file_contents=$(cat $filename) [...do some stuff...] echo $file_contents >> $new_filename 

Aus stdin lesen

Was das Lesen von stdin readangeht, haben die meisten Shells einen ziemlich standardmäßigen Standard, obwohl es Unterschiede gibt, wie die Eingabeaufforderungen (zumindest) angegeben werden.

Die Manpage von Bash builtins hat eine ziemlich kurze Erklärung read, aber ich bevorzuge die Seite von Bash Hackers .

Einfach:

read var_name 

Mehrere Variablen

Um mehrere Variablen festzulegen, geben Sie einfach mehrere Parameternamen an read:

read var1 var2 var3 

read fügt dann ein Wort aus stdin in jede Variable ein und speichert alle verbleibenden Wörter in die letzte Variable.

λ read var1 var2 var3 thing1 thing2 thing3 thing4 thing5 λ echo $var1; echo $var2; echo $var3 thing1 thing2 thing3 thing4 thing5 

Wenn weniger Wörter als Variablen eingegeben werden, sind die verbleibenden Variablen leer (auch wenn sie zuvor festgelegt wurden):

λ read var1 var2 var3 thing1 thing2 λ echo $var1; echo $var2; echo $var3 thing1 thing2 # Empty line 

Eingabeaufforderungen

Ich verwende -pFlag häufig für eine Eingabeaufforderung:

read -p "Enter filename: " filename 

Hinweis: ZSH und KSH (und möglicherweise andere) verwenden eine andere Syntax für Eingabeaufforderungen:

read "filename?Enter filename: " # Everything following the '?' is the prompt 

Standardwerte

Das ist nicht wirklich ein readTrick, aber ich benutze es oft in Verbindung mit read. Zum Beispiel:

read -p "Y/[N]: " reply reply=$ 

Wenn die Variable (Antwort) vorhanden ist, geben Sie sich selbst zurück. Wenn jedoch leer ist, geben Sie den folgenden Parameter ("N") zurück.

4
R..

Der einfachste Weg ist, stdin selbst umzuleiten:

if [ "$1" ] ; then exec < "$1" ; fi 

Oder wenn Sie die etwas knappere Form bevorzugen:

test "$1" && exec < "$1" 

Jetzt kann der Rest Ihres Skripts einfach von stdin gelesen werden. Natürlich können Sie mit dem erweiterten Parsing von Optionen ähnlich vorgehen, anstatt die Position des Dateinamens hart zu kodieren "$1".

`exec` wird versuchen, das Argument als Befehl auszuführen, was wir hier nicht wollen. Suzana vor 8 Jahren 0
@Suzana_K: Nicht wenn es keine Argumente hat, wie hier. In diesem Fall werden nur Dateibeschreibungen für die Shell selbst und nicht für einen untergeordneten Prozess ersetzt. R.. vor 8 Jahren 0
Ich habe `if [" $ 1 "] kopiert; dann exec <"$ 1"; fi` in einem Testskript gibt eine Fehlermeldung aus, da der Befehl unbekannt ist. Gleiches mit der knappen Form. Suzana vor 8 Jahren 0
@Suzana_K: Welche Shell benutzt du? Wenn das stimmt, handelt es sich nicht um eine funktionierende Implementierung des POSIX-Befehls sh / der Bourne-Shell. R.. vor 8 Jahren 1
GNU bash 4.3.11 unter Linux Mint Qiana Suzana vor 8 Jahren 0
@Suzana_K: Ich habe gerade mit Bash 4.3.33 getestet und es funktioniert gut. Ich vermute, Sie haben irgendwo im Skript einen Tippfehler oder eine verrückte Konfiguration wird verarbeitet. Geben Sie den Befehl interaktiv oder in ein Skript ein, das Sie ausführen? Vielleicht könnten Sie das Skript einbetten, wenn Sie möchten, dass ich es mir anschaue (oder, besser noch, stellen Sie hier eine Frage, warum es nicht funktioniert, obwohl es sollte). R.. vor 8 Jahren 0
Ja, das ist komisch. Ich möchte auch den Grund wissen. Ich habe die Ausgabe und das Skript an [this gist] gepostet (https://gist.github.com/SuzanaK/04a1a66b2fe77f107013). Suzana vor 8 Jahren 0
@Suzana_K Dieser Fehler tritt auf, weil "exec" versucht, die Datei "hallo" zu öffnen, die nicht vorhanden ist. Wenn Sie * in die Datei * schreiben möchten, klappen Sie `<` nach `>`. YoYoYonnY vor 6 Jahren 0
3
Aaron Davies

Verwenden Sie etwas anderes, das sich bereits so verhält, und verwenden Sie es "$@"

Sagen wir, ich möchte ein Werkzeug schreiben, das Leerzeichen im Text durch Tabulatoren ersetzt

trist der offensichtlichste Weg, dies zu tun, aber es nur akzeptiert stdin, also müssen wir Kette vom cat:

$ cat entab1.sh #!/bin/sh  cat "$@"|tr -s ' ' '\t' $ cat entab1.sh|./entab1.sh #!/bin/sh  cat "$@"|tr -s ' ' '\t' $ ./entab1.sh entab1.sh #!/bin/sh  cat "$@"|tr -s ' ' '\t' $  

Für ein Beispiel, bei dem sich das verwendete Werkzeug bereits so verhält, können Sie dies sedstattdessen erneut implementieren :

$ cat entab2.sh #!/bin/sh  sed -r 's/ +/\t/g' "$@" $  
2
Daniel

Sie können auch tun:

#!/usr/bin/env bash  # Set variable input_file to either $1 or /dev/stdin, in case $1 is empty # Note that this assumes that you are expecting the file name to operate on on $1 input_file="$"  # You can now use "$input_file" as your file to operate on cat "$input_file" 

Für mehr ordentlich Parameter Substitution Tricks in Bash, sieht dies .

Das ist fantastisch! Ich verwende `uglifyjs </ dev / stdin` und es funktioniert wunderbar! bfred.it vor 8 Jahren 1
0
RaamEE

Sie können es auch einfach halten und diesen Code verwenden


Wenn Sie eine Skriptdatei pass_it_on.sh mit diesem Code erstellen,

#!/bin/bash  cat 

Du kannst rennen

cat SOMEFILE.txt | ./pass_it_on.sh 

und der gesamte Inhalt von stdin wird einfach auf den Bildschirm ausgegeben.


Alternativ können Sie diesen Code verwenden, um eine Kopie von stdin in einer Datei zu behalten und dann auf den Bildschirm zu spucken.

#!/bin/bash  tmpFile=`mktemp` cat > $tmpFile cat $tmpFile  

und hier ist ein anderes, vielleicht lesbareres Beispiel, das hier erklärt wird:

http://mockingeye.com/blog/2013/01/22/reading-everything-stdin-in-a-bash-script/

#!/bin/bash  VALUE=$(cat)  echo "$VALUE" 

Habe Spaß.

RaamEE

0
kenorb

Der einfachste Weg und POSIX-kompatibel ist:

file=$ 

das ist äquivalent zu $.

Dann lesen Sie die Datei wie gewohnt:

while IFS= read -r line; do printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line" done < <(cat -- "$file")