Vă mulțumim pentru susținere

Shell scripting intrări de redirecționare ciudățenii

Poate cineva să explice acest comportament? Alergare:

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

nu are ca rezultat nimic, în timp ce:

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

produce rezultatul așteptat:

hello
world

Nu ar trebui ca conducta să facă într-un singur pas ce a făcut redirecționarea către test.file în al doilea exemplu? Am încercat același cod cu ambele cochilii de bord și bash și am primit același comportament de la amândouă.

0
adăugat editat

7 răspunsuri

Este pentru că versiunea conductei creează un subshell, care citește variabila în spațiul său local, care este distrus atunci când subshell-ul iese.

Executați această comandă

$ echo $$;cat | read a
10637

și folosiți pstree -p pentru a vă uita la procesele care rulează, veți vedea o cochilie extra agățată de cochilia dvs. principală.

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)
0
adăugat

Bine, mi-am dat seama!

Acesta este un bug greu de capturat, dar rezultă din modul în care conductele sunt manipulate de cochilie. Fiecare element al unei conducte se desfășoară într-un proces separat. Când comanda read stabilește var1 și var2, le stabilește propriile subshell-uri, nu shell-ul parental. Deci, atunci când subshell-ul iese, valorile var1 și var2 sunt pierdute. Cu toate acestea, puteți încerca să faceți

var1=$(echo "Hello")
echo var1

care returnează răspunsul așteptat. Din păcate, acest lucru funcționează numai pentru variabilele unice, nu puteți seta multe la un moment dat. Pentru a seta mai multe variabile la un moment dat, trebuie fie să citiți într-o singură variabilă și să o introduceți în mai multe variabile sau să folosiți ceva de genul:

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

În timp ce recunosc că nu este la fel de elegant ca utilizarea unei țevi, funcționează. Desigur, ar trebui să rețineți că citirea a fost menită să citească din fișiere în variabile, astfel încât a face citirea de la standard de intrare ar trebui să fie un pic mai greu.

0
adăugat
Bash 4.2 a introdus o opțiune pentru a face același lucru. Dezactivați controlul funcției ( set + m ) și setați opțiunea lastpipe ( shopt -s lastpipe ).
adăugat autor chepner
Acest lucru depinde de alegerea cochiliei. Ksh93 a fost conceput pentru a limita regia procesului. De asemenea, rulează elementul final al conductei în interiorul procesului invocând shell-ul, păstrând astfel statutul.
adăugat autor Henk Langeveld
Mă voi uita la asta. Mulțumiri!
adăugat autor Henk Langeveld

O adăugare recentă a bash este opțiunea lastpipe , care permite ultima comandă dintr-o conductă să ruleze în shell-ul curent, nu într-un subshell, atunci când controlul funcției este dezactivat.

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

va ieși într-adevăr

hello
world
0
adăugat

Încerca:

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

Problema, așa cum au declarat mai mulți oameni, este că var1 și var2 sunt create într-un mediu subshell care este distrus când acel subshell iese. Cele de mai sus evită distrugerea subsalvei până când rezultatul a fost ecou. O altă soluție este:

result=`echo "hello world"`
read var1 var2 <
0
adăugat

Acest lucru a fost deja corect răspuns, dar soluția nu a fost încă stabilită. Utilizați ksh, nu bash. Comparaţie:

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

La:

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

ksh este o coajă de programare superioară din cauza unor mici priceperi ca aceasta. (bash este cea mai bună coajă interactivă, în opinia mea.)

0
adăugat
read var1 var2 < <(echo "hello world")
0
adăugat
#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2

nu produce nici o ieșire, deoarece conductele rulează fiecare dintre componentele lor într-un subshell. Subshell-uri moștenește copii din variabilele shell-ului părinte, mai degrabă decât să le partajați. Incearca asta:

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

Parantezele definesc o regiune de cod care se execută într-un subshell și $ foo își păstrează valoarea inițială după ce a fost modificată în interiorul lor.

Încearcă acum:

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

Plăcile sunt doar pentru grupare, nu se creează un subshell și $ foo modificat în interiorul bretelelor este același $ foo modificat în afara lor.

Încearcă acum:

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

În interiorul bretelelor, cititul încorporat creează în mod corespunzător $ var1 și $ var2 și puteți vedea că ele sunt reluate. În afara bretelelor, ele nu mai există. Tot codul din bretele a fost rulat într-un subshell deoarece este o componentă a unei conducte .

Aveți posibilitatea să puneți cantități arbitrare de cod între acolade, astfel încât să puteți utiliza această construcție de conducte în blocuri ori de câte ori aveți nevoie pentru a rula un bloc de script shell care analizează rezultatul altceva.

0
adăugat
+1 pentru exemple curate - cod frumos, ușor de citit
adăugat autor Henk Langeveld