2008-08-05 17 views
16

Kann jemand dieses Verhalten erklären? Running:Shell-Skript Eingabe Umleitung Kuriositäten

#!/bin/sh 
echo "hello world" | read var1 var2 
echo $var1 
echo $var2 

Ergebnisse in nichts sein ouput, während:

#!/bin/sh 
echo "hello world" > test.file 
read var1 var2 < test.file 
echo $var1 
echo $var2 

die erwartete Ausgabe erzeugt:

hello 
world 

Sollte nicht das Rohr in einem Schritt tun, was die Umleitung test.file tat im zweiten Beispiel? Ich habe den gleichen Code sowohl mit der Bindestrich- als auch der Bash-Shell ausprobiert und habe das gleiche Verhalten von beiden bekommen.

Antwort

8

Ein kürzlich neben bash ist die lastpipe Option, die der letzte Befehl in einer Pipeline in dem aktuell Shell, keine Sub-Shell laufen zu lassen, wenn die Jobsteuerung deaktiviert ist.

#!/bin/bash 
set +m  # Deactiveate job control 
shopt -s lastpipe 
echo "hello world" | read var1 var2 
echo $var1 
echo $var2 

wird in der Tat Ausgabe

hello 
world 
3

Das liegt daran, dass die Pipe-Version eine Subshell erstellt, die die Variable in ihren lokalen Bereich liest, der dann beim Beenden der Subshell zerstört wird.

diesen Befehl ausführen

$ echo $$;cat | read a 
10637 

und verwenden pstree -p auf die laufenden Prozesse zu betrachten, werden Sie eine zusätzliche Hülle aus Ihrer Hauptschale hängen sehen.

|      |-bash(10637)-+-bash(10786) 
    |      |    `-cat(10785) 
5

Alles klar, ich habe es herausgefunden!

Dies ist ein schwerer Fehler, der sich jedoch aus der Art ergibt, wie Pipes von der Shell gehandhabt werden. Jedes Element einer Pipeline wird in einem separaten Prozess ausgeführt. Wenn der Lesebefehl var1 und var2 einstellt, setzt er sie auf eine eigene Subshell, nicht auf die Parent-Shell. Wenn die Subshell beendet wird, gehen die Werte von var1 und var2 verloren. Sie können jedoch versuchen

var1=$(echo "Hello") 
echo var1 

tun, die die erwartete Antwort zurückgibt. Leider funktioniert dies nur für einzelne Variablen, Sie können nicht viele gleichzeitig setzen. Um mehrere Variablen zu einer Zeit festlegen, die Sie entweder in eine Variable lesen muss und es zerhacken in mehrere Variablen oder so etwas wie folgt verwenden:

set -- $(echo "Hello World") 
var1="$1" var2="$2" 
echo $var1 
echo $var2 

Während ich es nicht so elegant wie mit einem Rohr zugeben, es funktioniert . Natürlich sollte man im Hinterkopf behalten, dass Lesen dazu gedacht ist, Dateien in Variablen zu lesen. Daher sollte es etwas schwieriger sein, aus der Standardeingabe zu lesen.

+2

Dieses von der Wahl der Schale abhängt. Ksh93 wurde entwickelt, um den Prozessaufwand zu begrenzen.Es führt auch das letzte Element einer Pipeline * innerhalb des aufrufenden Shell-Prozesses aus, wodurch der Zustand erhalten bleibt. –

+1

Bash 4.2 führte eine Option ein, um das Gleiche zu tun. Schalten Sie die Jobsteuerung aus ('set + m') und setzen Sie die Option' lastpipe' ('shopt -s lastpipe'). – chepner

+0

Ich werde das untersuchen. Vielen Dank! –

9

Dies wurde bereits korrekt beantwortet, aber die Lösung wurde noch nicht festgelegt. Verwenden Sie ksh, nicht bash. Vergleichen:

$ echo 'echo "hello world" | read var1 var2 
echo $var1 
echo $var2' | bash -s 

An:

$ echo 'echo "hello world" | read var1 var2 
echo $var1 
echo $var2' | ksh -s 
hello 
world 

KSH wegen kleinen Feinheiten wie diese eine bessere Programmierung Shell ist. (Bash ist die bessere interaktive Shell, meiner Meinung nach.)

3

Der Beitrag wurde richtig beantwortet, aber ich möchte einen alternativen One-Liner, der vielleicht von Nutzen sein könnte.

Für Raum getrennt Werte von Echo Zuweisung (oder was das betrifft stdout) Variablen zu schälen, könnte man unter Verwendung von Schalenanordnungen zu berücksichtigen:

$ var=($(echo 'hello world')) 
$ echo ${var[0]} 
hello 
$ echo ${var[1]} 
world 

In diesem Beispiel var ist ein Array, und die Inhalte zugegriffen werden kann das Konstrukt $ {var [index]}, wobei index der Array-Index ist (beginnt mit 0).

Auf diese Weise können Sie dem relevanten Array-Index so viele Parameter zuweisen, wie Sie möchten.

+2

Gute Lösung. Es funktioniert gut in bash, aber es funktioniert nicht in der Dash-Shell. –

8
read var1 var2 < <(echo "hello world") 
3

Versuchen:

echo "hello world" | (read var1 var2 ; echo $var1 ; echo $var2) 

Das Problem, da mehrere Leute gesagt haben, ist, dass var1 und var2 in einer Sub-Shell-Umgebung geschaffen, die, wenn die betreffende Subshell Ausfahrten zerstört wird. Das oben genannte vermeidet die Zerstörung der Subshell, bis das Ergebnis echo ist. Eine weitere Lösung ist:

result=`echo "hello world"` 
read var1 var2 <<EOF 
$result 
EOF 
echo $var1 
echo $var2 
10
#!/bin/sh 
echo "hello world" | read var1 var2 
echo $var1 
echo $var2 

erzeugt keine Ausgabe, da Pipelines jeder ihrer Komponenten in einem Sub-Shell laufen. Subshells erben Kopien der Variablen der übergeordneten Shell, anstatt sie zu teilen. Versuchen Sie folgendes:

#!/bin/sh 
foo="contents of shell variable foo" 
echo $foo 
(
    echo $foo 
    foo="foo contents modified" 
    echo $foo 
) 
echo $foo 

Die Klammern einen Bereich Code definieren, die in einer Subshell ausgeführt wird, und $ foo behält seinen ursprünglichen Wert nach in ihnen modifiziert.

Nun ist diese versuchen:

#!/bin/sh 
foo="contents of shell variable foo" 
echo $foo 
{ 
    echo $foo 
    foo="foo contents modified" 
    echo $foo 
} 
echo $foo 

Die Streben rein für die Gruppierung sind, wird keine Sub-Shell erstellt, und die $ foo in den geschweiften Klammern modifiziert ist die gleiche $ foo außerhalb sie geändert.

Nun ist diese versuchen:

#!/bin/sh 
echo "hello world" | { 
    read var1 var2 
    echo $var1 
    echo $var2 
} 
echo $var1 
echo $var2 

Innerhalb der geschweiften Klammern, gebautet die Lese schafft $ var1 und $ var2 richtig und Sie können sehen, dass sie Echo erhalten. Außerhalb der Zahnspangen existieren sie nicht mehr. Der gesamte Code innerhalb der geschweiften Klammern wurde in einer Subshell ausgeführt, weil es eine Komponente einer Pipeline ist.

Sie können beliebigen Code in geschweifte Klammern setzen, sodass Sie diese Pipeline-in-einem-Block-Konstruktion immer dann verwenden können, wenn Sie einen Shellskriptblock ausführen müssen, der die Ausgabe von etwas anderem analysiert.

+1

+1 für saubere Beispiele - schöner, lesbarer Code –

4

Meine Meinung zu diesem Thema (mit Bash):

read var1 var2 <<< "hello world" 
echo $var1 $var2