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ă.
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)
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.
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.)
Postul a primit un răspuns corect, dar aș dori să ofer o altă linie de transport care ar putea fi de folos.
Pentru a atribui valori separate de spațiu de la ecou (sau stdout pentru acel lucru) la variabilele shell, puteți lua în considerare utilizarea matricelor coajă:
$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world
În acest exemplu, var este o matrice și conținutul poate fi accesat folosind structura $ {var [index]}, unde indexul este indexul matricei (începe cu 0).
În acest fel puteți avea cât mai mulți parametri pe care doriți să li se atribuie indexului de matrice relevant.
read var1 var2 < <(echo "hello world")
Î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 <
#!/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.
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