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

57

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

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

gibt es einen einfachen "out of the box" Weg, dies zu tun? Oder muss ich das Rad neu erfinden und ein wenig Logik in das Skript schreiben?

Gilad hoch
quelle
@PlasmaPower Solange sich die Frage auf SU bezieht, ist es nicht erforderlich , sie auf einer anderen SE-Site zu stellen. Viele SE-Sites haben Überlappungen. Im Allgemeinen brauchen wir keine überlappende Site vorzuschlagen, es sei denn, die Frage ist entweder nicht thematisch (in diesem Fall Abstimmung für Migration) oder thematisch, aber es wird nicht viel darauf geantwortet (in diesem Fall sollte der Fragesteller als Moderator markieren). Aufmerksamkeit / Migration, nicht postübergreifend).
Bob

Antworten:

59

Wenn das Dateiargument das erste Argument für Ihr Skript ist, prüfen Sie, ob es ein Argument ( $1) und eine Datei gibt. Sonst lese die Eingabe von stdin -

Ihr Skript könnte also etwa Folgendes 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

Bearbeiten Eine Erklärung des Skripts:

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

&&ist der Shell-logische AND-Operator. Wenn der Test wahr ist, wird die Datei zugewiesen input="$1"und cat $inputausgegeben.

||ist der logische ODER-Operator der Shell. Wenn der Test falsch ist, werden die folgenden Befehle ||analysiert. Eingang ist mit "-" belegt. Der Befehl cat -liest von der Tastatur.

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

Verdächtiger
quelle
Was macht && input="$1" || input="-" und warum ist es außerhalb des testBetreibers?
13.
Ich habe eine Bearbeitung mit einigen Erklärungen hinzugefügt, von denen ich hoffe, dass sie helfen werden.
Verdächtiger
Was ist, wenn das Skript mehrere Argumente hat ( $@)?
g33kz0r
12

readLiest von der Standardeingabe. Das Umleiten von file ( ./script <someinput) oder through pipe ( dosomething | ./script) führt nicht zu einer anderen Funktionsweise.

Alles, was Sie tun müssen, ist, alle Zeilen in der Eingabe zu durchlaufen (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 |).

Lukasz Daniluk
quelle
Vielen Dank! Ich wähle die andere Antwort, weil sie mir besser passt. Ich habe ein anderes Skript geschrieben und wollte keine Schleife ausführen, bis alle Eingaben eingegangen sind (könnte eine Menge Eingaben sein ... wäre verschwenderisch).
Gilad Hoch
4

Sie erwähnen nicht, welche Shell Sie verwenden möchten, also nehme ich bash an, obwohl dies bei allen Shells ziemlich übliche Dinge sind.

Datei Argumente

Auf Argumente kann über die Variablen zugegriffen werden $1- $n( $0gibt den Befehl zurück, der zum Ausführen des Programms verwendet wird). Angenommen, ich habe ein Skript, das nur catn Dateien mit einem Trennzeichen dazwischen enthält:

#!/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 jedoch die Daten in der Datei transformieren möchten (ohne sie explizit zu schreiben und neu zu schreiben), können Sie den Dateiinhalt auch in einer Variablen speichern:

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

Lesen Sie von stdin

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

Die Manpage von Bash builtins hat eine ziemlich knappe 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 Setzt dann ein Wort von stdin in jede Variable, wobei alle verbleibenden Wörter in die letzte Variable geschrieben werden.

λ 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 benutze -pflag oft für eine Eingabeaufforderung:

read -p "Enter filename: " filename

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

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

Standardwerte

Das ist kein wirklicher readTrick, aber ich benutze ihn oft in Verbindung mit read. Zum Beispiel:

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

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

Jacob Hayes
quelle
4

Der einfachste Weg ist, stdin selbst umzuleiten:

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

Oder wenn Sie die knappere Form bevorzugen:

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

Jetzt kann der Rest Ihres Skripts nur noch von stdin lesen. Natürlich können Sie die Position des Dateinamens auch mit erweiterten Optionen analysieren, anstatt sie fest zu codieren "$1".

R ..
quelle
execIch werde versuchen, das Argument als Befehl auszuführen, was wir hier nicht wollen.
Suzana
@Suzana_K: Nicht wenn es keine Argumente gibt, wie hier. In diesem Fall werden nur die Dateideskriptoren für die Shell selbst und nicht für einen untergeordneten Prozess ersetzt.
R ..
Ich habe if [ "$1" ] ; then exec < "$1" ; fiin einem Testskript kopiert und es wird eine Fehlermeldung ausgegeben, da der Befehl unbekannt ist. Gleiches gilt für die knappe Form.
Suzana
1
@Suzana_K: Welche Shell benutzt du? Wenn das stimmt, ist es keine funktionierende Implementierung des POSIX-Befehls sh / Bourne-Shell.
R ..
GNU-Bash 4.3.11 unter Linux Mint Qiana
Suzana
3

benutze (oder verkette) etwas anderes, das sich bereits so verhält, und benutze "$@"

Angenommen, ich möchte ein Tool schreiben, das Leerzeichen in Text durch Tabulatoren ersetzt

trist der naheliegendste Weg, dies zu tun, aber er akzeptiert nur stdin, also müssen wir verketten von 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, in dem sich das verwendete Tool bereits so verhält, könnten wir dies sedstattdessen mit neu implementieren :

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 
Aaron Davies
quelle
3

Sie können auch:

#!/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="${1:-/dev/stdin}"

# You can now use "$input_file" as your file to operate on
cat "$input_file"

Weitere nützliche Tricks zum Ersetzen von Parametern in Bash finden Sie hier .

Daniel
quelle
1
Das ist fantastisch! Ich benutze uglifyjs < /dev/stdinund es funktioniert wunderbar!
Fregante
0

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 gespritzt.


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

#!/bin/bash

tmpFile=`mktemp`
cat > $tmpFile
cat $tmpFile    

und hier ist ein weiteres Beispiel, vielleicht besser lesbar, 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

RaamEE
quelle
0

Der einfachste Weg und POSIX-konform ist:

file=${1--}

das ist äquivalent zu ${1:--}.

Dann lies die Datei wie gewohnt:

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