it-swarm.com.de

Liest Werte aus einer Pipe in eine Shell-Variable

Ich versuche, Bash zu bekommen, um Daten von stdin zu verarbeiten, die in geleitet werden, aber kein Glück. Was ich meine, ist keine der folgenden Arbeiten:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

wo ich die Ausgabe haben möchte test=hello world. Ich habe versucht, Anführungszeichen "" um "$test" Zu setzen, die auch nicht funktionieren.

189
ldog

Verwenden

IFS= read var << EOF
$(foo)
EOF

Sie can tricksen read so aus einer Pipe zu akzeptieren:

echo "hello world" | { read test; echo test=$test; }

oder schreibe sogar eine Funktion wie diese:

read_from_pipe() { read "[email protected]" <&0; }

Aber es hat keinen Sinn - Ihre variablen Zuweisungen können nicht von Dauer sein! Eine Pipeline kann eine Subshell erzeugen, bei der die Umgebung nicht durch Verweis, sondern durch Wert vererbt wird. Aus diesem Grund kümmert sich read nicht um die Eingabe aus einer Pipe - es ist undefiniert.

FYI, http://www.etalabs.net/sh_tricks.html ist eine raffinierte Sammlung der Cruft, die notwendig ist, um die Kuriositäten und Inkompatibilitäten von Borowski-Muscheln zu bekämpfen, sh.

157
yardena

wenn Sie viele Daten einlesen und jede Zeile einzeln bearbeiten möchten, können Sie Folgendes verwenden:

cat myFile | while read x ; do echo $x ; done

wenn Sie die Zeilen in mehrere Wörter aufteilen möchten, können Sie mehrere Variablen anstelle von x wie folgt verwenden:

cat myFile | while read x y ; do echo $y $x ; done

alternative:

while read x y ; do echo $y $x ; done < myFile

Aber sobald Sie anfangen, etwas wirklich Kluges mit solchen Dingen anzustellen, sollten Sie sich für eine Skriptsprache wie Perl entscheiden, in der Sie Folgendes ausprobieren können:

Perl -ane 'print "$F[0]\n"' < myFile

Perl hat eine ziemlich steile Lernkurve (oder ich denke, eine dieser Sprachen), aber Sie werden es auf lange Sicht viel einfacher finden, wenn Sie etwas anderes als die einfachsten Skripte ausführen möchten. Ich würde das Perl-Kochbuch und natürlich die Perl-Programmiersprache von Larry Wall et al. Empfehlen.

105
Nick

read liest nicht aus einer Pipe (oder das Ergebnis geht möglicherweise verloren, weil die Pipe eine Subshell erstellt). Sie können jedoch einen Here-String in Bash verwenden:

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

Weitere Informationen zu lastpipe finden Sie in der Antwort von @ chepner.

38

Dies ist eine weitere Option

$ read test < <(echo hello world)

$ echo $test
hello world
38
Steven Penny

Ich bin kein Experte für Bash, aber ich frage mich, warum dies nicht vorgeschlagen wurde:

stdin=$(cat)

echo "$stdin"

Einzeiliger Beweis, dass es bei mir funktioniert:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'
30
djanowski

bash 4.2 führt die Option lastpipe ein, mit der Ihr Code so funktioniert, wie er geschrieben wurde, indem der letzte Befehl in einer Pipeline in der aktuellen Shell und nicht in einer Subshell ausgeführt wird.

shopt -s lastpipe
echo "hello world" | read test; echo test=$test
22
chepner

Die Syntax für eine implizite Pipe von einem Shell-Befehl in eine Bash-Variable lautet

var=$(command)

oder

var=`command`

In Ihren Beispielen leiten Sie Daten an eine Zuweisungsanweisung weiter, die keine Eingabe erwartet.

13
mob

Der erste Versuch war ziemlich knapp. Diese Variante sollte funktionieren:

echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

und die Ausgabe ist:

test = Hallo Welt

Sie benötigen nach der Pipe geschweifte Klammern, um die zu testende Aufgabe und das Echo einzuschließen.

Ohne die geschweiften Klammern befindet sich die Zuweisung für test (nach der Pipe) in einer Shell, und das Echo "test = $ test" befindet sich in einer separaten Shell, die nichts über diese Zuweisung weiß. Aus diesem Grund wurde in der Ausgabe "test =" anstelle von "test = hello world" angezeigt.

6
jbuhacoff

Weil ich darauf hereinfalle, möchte ich eine Notiz fallen lassen. Ich habe diesen Thread gefunden, weil ich ein altes sh-Skript neu schreiben muss, um POSIX-kompatibel zu sein. Dies bedeutet im Grunde, das von POSIX eingeführte Pipe-/Subshell-Problem zu umgehen, indem Sie den Code folgendermaßen umschreiben:

some_command | read a b c

in:

read a b c << EOF
$(some_command)
EOF

Und Code wie folgt:

some_command |
while read a b c; do
    # something
done

in:

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

Letzteres verhält sich jedoch bei Leereingaben nicht gleich. Bei der alten Notation wird die while-Schleife nicht bei leerer Eingabe eingegeben, sondern bei der POSIX-Notation! Ich denke, es liegt an der Newline vor EOF, die nicht ausgelassen werden kann. Der POSIX-Code, der sich eher wie die alte Notation verhält, sieht folgendermaßen aus:

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

In den meisten Fällen sollte dies gut genug sein. Aber leider verhält sich dies immer noch nicht genau wie die alte Notation, wenn some_command eine leere Zeile ausgibt. In der alten Notation wird der while-Body ausgeführt und in der POSIX-Notation brechen wir vor dem Body.

Ein Lösungsansatz könnte folgendermaßen aussehen:

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF
5
hd-quadrat

In meinen Augen ist der beste Weg, um aus stdin in bash zu lesen, der folgende, mit dem Sie auch an den Zeilen arbeiten können, bevor die Eingabe endet:

while read LINE; do
    echo $LINE
done < /dev/stdin
5
Jaleks

Das Einfügen von etwas in einen Ausdruck, der eine Aufgabe enthält, verhält sich nicht so.

Versuchen Sie stattdessen Folgendes:

test=$(echo "hello world"); echo test=$test
3
bta

Der folgende Code:

echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test )

wird auch funktionieren, aber es wird eine weitere neue Sub-Shell nach der Pipe geöffnet, wo

echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; }

gewohnheit.


Ich musste die Jobsteuerung deaktivieren, um die Methode chepnars 'zu verwenden (ich habe diesen Befehl vom Terminal aus ausgeführt):

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Bash Manual sagt :

letzte Pfeife

Wenn gesetzt und die Jobsteuerung nicht aktiv ist , führt die Shell den letzten Befehl einer Pipeline aus, die in der aktuellen Shell-Umgebung nicht im Hintergrund ausgeführt wird.

Hinweis: Die Auftragssteuerung ist in einer nicht interaktiven Shell standardmäßig deaktiviert, sodass Sie das set +m In einem Skript nicht benötigen.

2
Jahid

Ich glaube, Sie haben versucht, ein Shell-Skript zu schreiben, das Eingaben von stdin entgegennehmen kann. Aber während Sie versuchen, dies inline zu tun, haben Sie sich beim Versuch, diese test = -Variable zu erstellen, verirrt. Ich denke, es macht nicht viel Sinn, es inline zu machen, und deshalb funktioniert es nicht so, wie Sie es erwarten.

Ich habe versucht zu reduzieren

$( ... | head -n $X | tail -n 1 )

um eine bestimmte Zeile von verschiedenen Eingaben zu erhalten. also könnte ich tippen ...

cat program_file.c | line 34

deshalb brauche ich ein kleines Shell-Programm, das von stdin lesen kann. so wie du es tust.

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

los gehts.

1
Mathieu J.

Ein intelligentes Skript, das sowohl Daten aus PIPE als auch Befehlszeilenargumente lesen kann:

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "[email protected]"

Ausgabe:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

Erklärung: Wenn ein Skript Daten über eine Pipe empfängt, ist stdin/proc/self/fd/0 ein Symlink zu einer Pipe.

/proc/self/fd/0 -> pipe:[155938]

Wenn nicht, zeigt es auf das aktuelle Terminal:

/proc/self/fd/0 -> /dev/pts/5

Die Bash if -p Option kann überprüfen, ob es sich um eine Pipe handelt oder nicht.

cat - liest das aus stdin.

Wenn wir cat - Wenn es kein stdin gibt, wird es für immer warten, deshalb setzen wir es in die if Bedingung.

0
Seff