Lesen Sie Werte aus einer Pipe in eine Shell-Variable


205

Ich versuche, bash dazu zu bringen, Daten von stdin zu verarbeiten, in die weitergeleitet wird, 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 zu setzen "$test", was auch nicht funktioniert.


1
Ihr Beispiel .. Echo "Hallo Welt" | Test lesen; echo test = $ test hat bei mir gut funktioniert .. Ergebnis: test = hallo Welt; In welcher Umgebung läuft das? Ich benutze Bash 4.2 ..
Alex.Pilon

Möchten Sie mehrere Zeilen in einem Lesevorgang? Ihr Beispiel zeigt nur eine Zeile, aber die Problembeschreibung ist unklar.
Charles Duffy

2
@ alex.pilon, ich verwende Bash Version 4.2.25 und sein Beispiel funktioniert auch nicht für mich. Möglicherweise handelt es sich um eine Bash-Laufzeitoption oder eine Umgebungsvariable? Ich habe das Beispiel funktioniert auch nicht mit Sh, also kann Bash versuchen, mit Sh kompatibel zu sein?
Hibou57

2
@ Hibou57 - Ich habe es in Bash 4.3.25 erneut versucht und es funktioniert nicht mehr. Mein Gedächtnis ist diesbezüglich verschwommen und ich bin mir nicht sicher, was ich getan haben könnte, um es zum Laufen zu bringen.
Alex.pilon

2
@ Hibou57 @ alex.pilon das letzte cmd in einer Pipe sollte die vars in bash4> = 4.2 mit shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
imz - Ivan Zakharyaschev

Antworten:


162

Verwenden

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

Sie können versuchen read, von einer Pipe wie folgt zu akzeptieren:

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

oder schreiben Sie sogar eine Funktion wie diese:

read_from_pipe() { read "$@" <&0; }

Aber es hat keinen Sinn - Ihre Variablenzuweisungen halten möglicherweise nicht an! Eine Pipeline kann eine Subshell erzeugen, in der die Umgebung nach Wert und nicht nach Referenz vererbt wird. Aus diesem Grund readkümmert es sich nicht um die Eingabe von einer Pipe - sie ist undefiniert.

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


Sie können die Zuweisung zuletzt vornehmen, indem Sie stattdessen Folgendes tun: `test =` `echo" Hallo Welt "| {Test lesen; echo $ test; } `` `
Compholio

2
Versuchen wir es noch einmal (anscheinend macht es Spaß, Backticks in diesem Markup zu entkommen):test=`echo "hello world" | { read test; echo $test; }`
Compholio,

kann ich fragen , warum Sie verwendet {}statt ()in Gruppieren dieser beiden Befehle?
Jürgen Paul

4
Der Trick besteht nicht darin read, Eingaben von der Pipe vorzunehmen, sondern die Variable in derselben Shell zu verwenden, in der die ausgeführt wird read.
Chepner

1
Wurde bash permission deniedbeim Versuch, diese Lösung zu verwenden. Mein Fall ist ganz anders, aber ich konnte nirgendwo eine Antwort darauf finden. Was für mich funktioniert hat, war (ein anderes Beispiel, aber eine ähnliche Verwendung) : pip install -U echo $(ls -t *.py | head -1). Für den Fall, dass jemand jemals ein ähnliches Problem hat und auf diese Antwort stößt wie ich.
ivan_bilan

111

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 anstelle von x mehrere Variablen 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 zu machen, sollten Sie sich für eine Skriptsprache wie Perl entscheiden, in der Sie so etwas ausprobieren können:

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

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


8
"alternativ" ist der richtige Weg. Keine UUoC und keine Subshell. Siehe BashFAQ / 024 .
Bis auf weiteres angehalten.

43

readliest nicht aus einer Pipe (oder möglicherweise geht das Ergebnis verloren, weil die Pipe eine Unterschale erstellt). Sie können jedoch eine Here-Zeichenfolge in Bash verwenden:

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

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


Schöner und einfacher Einzeiler, leicht zu verstehen. Diese Antwort braucht mehr Upvotes.
David Parks

42

Dies ist eine weitere Option

$ read test < <(echo hello world)

$ echo $test
hello world

24
Der wesentliche Vorteil <(..)gegenüber $(..)besteht darin, dass <(..)jede Zeile an den Anrufer zurückgegeben wird, sobald der von ihm ausgeführte Befehl sie verfügbar macht. $(..)Wartet jedoch darauf, dass der Befehl seine gesamte Ausgabe abschließt und generiert, bevor er dem Aufrufer eine Ausgabe zur Verfügung stellt.
Derek Mahar

37

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"'

4
Dies liegt wahrscheinlich daran, dass "read" ein Bash-Befehl ist und cat eine separate Binärdatei ist, die in einem Unterprozess gestartet wird, sodass sie weniger effizient ist.
DJ_Segfault

10
manchmal trumpfen Einfachheit und Klarheit Effizienz :)
Rondo

5
definitiv die
direkteste

Das Problem, auf das ich gestoßen bin, ist, dass das Skript hängt, wenn keine Pipe verwendet wird.
Dale Anderson

@ DaleA Na klar. Jedes Programm, das versucht, von der Standardeingabe zu lesen, "hängt", wenn ihm nichts zugeführt wird.
Djanowski

27

bash4.2 führt die lastpipeOption ein, mit der Ihr Code wie geschrieben funktioniert, indem der letzte Befehl in einer Pipeline in der aktuellen Shell anstelle einer Subshell ausgeführt wird.

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

2
Ah! so gut das. Beim Testen in einer interaktiven Shell auch: "set + m" (in einem .sh-Skript nicht erforderlich)
XXL

14

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.


4
Weil $ () leicht verschachtelt werden kann. Denken Sie an JAVA_DIR = $ (dirname $ (readlink -f $ (welches Java))) und versuchen Sie es mit `. Sie müssen dreimal fliehen!
Albfan

8

Der erste Versuch war ziemlich nah. 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 Zuordnung und das Echo einzuschließen.

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


8

In meinen Augen ist der beste Weg, um von 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

1
Ich wurde fast verrückt, bevor ich das fand. Vielen Dank für das Teilen!
Samy Dindane

6

Weil ich darauf hereinfalle, möchte ich eine Notiz hinterlassen. 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 verursachte Rohr- / Unterschalenproblem zu umgehen, indem Code wie folgt umgeschrieben wird:

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 leerer Eingabe nicht gleich. Mit der alten Notation wird die while-Schleife nicht bei leerer Eingabe eingegeben, sondern in der POSIX-Notation! Ich denke, es liegt an der Newline vor EOF, die nicht weggelassen 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. Leider verhält sich dies immer noch nicht genau wie die alte Notation, wenn some_command eine leere Zeile druckt. In der alten Notation wird der while-Körper ausgeführt und in der POSIX-Notation brechen wir vor dem Körper.

Ein Ansatz, um dies zu beheben, 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

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

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

Ausgabe:

$ bash test arg1 arg2
ARGS=arg1 arg2

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

Erläuterung: 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- [[ -pOption kann überprüfen, ob es sich um eine Pipe handelt oder nicht.

cat -liest das von stdin.

Wenn wir verwenden, cat -wenn es keine gibt stdin, wird es ewig warten, deshalb setzen wir es in den ifZustand.


Sie können auch /dev/stdineinen Link zu/proc/self/fd/0
Elie G.

3

Etwas in einen Ausdruck mit einer Aufgabe zu leiten, verhält sich nicht so.

Versuchen Sie stattdessen:

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

2

Der folgende Code:

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

wird auch funktionieren, aber es wird eine weitere neue Unterschale nach dem Rohr öffnen, wo

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

Gewohnheit.


Ich musste die Jobsteuerung deaktivieren, um die Methode von chepnars verwenden zu können (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 :

Lastpipe

Wenn festgelegt 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 Jobsteuerung ist in einer nicht interaktiven Shell standardmäßig deaktiviert, sodass Sie das set +mInnere eines Skripts nicht benötigen .


1

Ich denke, Sie haben versucht, ein Shell-Skript zu schreiben, das Eingaben von stdin entgegennehmen kann. Aber während Sie versuchen, es inline zu tun, haben Sie sich beim Versuch, diese test = -Variable zu erstellen, verlaufen. 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. damit ich tippen konnte ...

cat program_file.c | line 34

Ich brauche also 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 geht's


-1

Wie wäre es damit:

echo "hello world" | echo test=$(cat)

Grundsätzlich ein Betrug aus meiner Antwort unten.
Djanowski

Vielleicht würde ich behaupten, meine ist etwas sauberer und näher an dem Code, der in der ursprünglichen Frage veröffentlicht wurde.
Bingles
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.