Mă întrebam cum să înțeleg următoarele:
Piparea stdout-ului unei comenzi în stdin-ul altei este o tehnică puternică. Dar, ce se întâmplă dacă aveți nevoie să introduceți mai multe comenzi? Aici intervine substituția de proces.
Cu alte cuvinte, substituția de proces poate face orice poate face conducta?
Ce poate face înlocuirea procesului, dar pipa nu poate?
Răspuns
O modalitate bună de a ascunde diferența dintre ele este să faci un pic de experimentare pe linia de comandă. În ciuda similitudinii vizuale în utilizarea caracterului <
, face ceva foarte diferit decât o redirecționare sau o conductă.
Să folosim date
comandă pentru testare.
$ date | cat Thu Jul 21 12:39:18 EEST 2011
Acesta este un exemplu inutil, dar arată că cat
a acceptat ieșirea date
pe STDIN și a scuipat-o înapoi. Aceleași rezultate pot fi obținute prin înlocuirea procesului:
$ cat <(date) Thu Jul 21 12:40:53 EEST 2011
Cu toate acestea, ceea ce tocmai s-a întâmplat în culise a fost diferit. În loc să i se ofere un flux STDIN, cat
a primit de fapt numele unui fișier de care avea nevoie pentru a fi deschis și citiți. Puteți vedea acest pas folosind echo
în loc de cat
.
$ echo <(date) /proc/self/fd/11
Când pisica a primit numele fișierului, a citit conținutul fișierului pentru noi. Pe de altă parte, echo tocmai ne-a arătat numele fișierului că a fost transmis. Această diferență devine mai evidentă dacă adăugați mai multe înlocuiri:
$ cat <(date) <(date) <(date) Thu Jul 21 12:44:45 EEST 2011 Thu Jul 21 12:44:45 EEST 2011 Thu Jul 21 12:44:45 EEST 2011 $ echo <(date) <(date) <(date) /proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13
este posibil să combinați substituirea procesului (care generează un fișier) și redirecționarea intrării (care conectează un fișier la STDIN):
$ cat < <(date) Thu Jul 21 12:46:22 EEST 2011
Arată cam la fel, dar de data aceasta cat a fost transmis fluxului STDIN în locul unui nume de fișier. Puteți vedea acest lucru încercând-o cu echo:
$ echo < <(date) <blank>
Deoarece echo nu citește STDIN și nu s-a trecut niciun argument, nu primim nimic.
Conductele și redirecționările de intrare împing conținutul pe fluxul STDIN. Înlocuirea procesului execută comenzile, salvează ieșirea într-un fișier temporar special și apoi trece numele fișierului în locul comenzii. Orice comandă pe care o utilizați o tratează ca un nume de fișier. Rețineți că fișierul creat nu este un fișier obișnuit, ci o țeavă numită care se elimină automat odată ce nu mai este necesar.
Comentarii
Răspuns
Iată trei lucruri pe care le puteți face cu înlocuirea procesului, care altfel sunt imposibile.
Intrări multiple de proces
diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)
pur și simplu nu există nicio modalitate de a face acest lucru cu țevi.
Conservarea STDIN
Spuneți că aveți următoarele:
curl -o - http://example.com/script.sh #/bin/bash read LINE echo "You said ${LINE}!"
Și doriți să îl rulați direct. Următoarele eșuează lamentabil. Bash folosește deja STDIN pentru a citi scriptul, așa că alte intrări sunt imposibile.
curl -o - http://example.com/script.sh | bash
Dar acest lucru funcționează perfect.
bash <(curl -o - http://example.com/script.sh)
Înlocuirea procesului de ieșire
De asemenea, rețineți că substituirea procesului funcționează și în sens invers. Deci, puteți face așa ceva:
(ls /proc/*/exe >/dev/null) 2> >(sed -n \ "/Permission denied/ s/.*\(\/proc.*\):.*/\1/p" > denied.txt )
Acesta este un exemplu complicat, dar trimite stdout către /dev/null
, în timp ce canalizați stderr într-un script sed pentru a extrage numele fișierelor pentru care o ” permisie refuzată ” a fost afișată eroarea și apoi trimite ACELE rezultate într-un fișier.
Rețineți că prima comandă și redirecționarea stdout sunt între paranteze ( subshell ) astfel încât numai rezultatul comenzii THAT să fie trimis la /dev/null
și să nu se încurce cu restul liniei.
Comentarii
- Merită ‘ de remarcat faptul că în
diff
exemplu, poate doriți să vă intereseze cazul în carecd
ar putea eșua:diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls)
. - ” în timp ce canalizați stderr „: nu este ‘ t subliniați că aceasta nu este nu canalizare, ci trece printr-un fișier fifo?
- @Gauthier nu; comanda este substituită nu cu un fifo, ci cu o referință la descriptorul de fișiere. Deci, ” echo < (echo) ” ar trebui să producă ceva de genul ” / dev / fd / 63 „, care este un dispozitiv cu caractere speciale care citește sau scrie din numărul FD 63.
Răspuns
Ar trebui să presupun că vorbești despre bash
sau despre un alt shell avansat, deoarece shell nu are substituire proces .
bash
rapoarte de pagină manuală:
Înlocuirea procesului
Înlocuirea procesului este acceptată pe sistemele care acceptă țevi numite (FIFO) sau metoda / dev / fd de denumire a fișierelor deschise. Acesta ia forma < (listă) sau> (listă). Lista proceselor este executată cu intrarea sau ieșirea conectată la un FIFO sau la un fișier din / dev / fd. Numele acestui fișier este transmis ca argument la comanda curentă ca rezultat al extinderii. Dacă se utilizează formularul> (listă), scrierea în fișier va furniza intrare pentru listă. Dacă se utilizează formularul < (list), fișierul transmis ca argument trebuie citit pentru a obține rezultatul listei.Când este disponibil, înlocuiți procesul se efectuează simultan cu extinderea parametrilor și variabilelor, substituirea comenzilor și extinderea aritmetică.
Cu alte cuvinte și din punct de vedere practic, puteți utiliza o expresie ca următoarea
<(commands)
ca nume de fișier pentru alte comenzi care necesită un fișier ca parametru. Sau puteți utiliza redirecționarea pentru un astfel de fișier:
while read line; do something; done < <(commands)
Revenind la întrebarea dvs., mi se pare că substituirea procesului și conductele nu au prea multe în comun.
Dacă doriți să canalizați în ordine ieșirea mai multor comenzi, puteți utiliza una dintre următoarele forme:
(command1; command2) | command3 { command1; command2; } | command3
poate utiliza, de asemenea, redirecționarea la substituirea procesului
command3 < <(command1; command2)
în cele din urmă, dacă command3
acceptă un parametru de fișier (în locul stdin )
command3 <(command1; command2)
Comentarii
- deci < ( ) și < < () are același efect, nu?
- @solfish: nu exacllty: firse poate fi folosit oriunde se așteaptă un nume de fișier, al doilea este o redirecționare de intrare pentru acel nume de fișier
Răspuns
Dacă un comanda ia o listă de fișiere ca argumente și procesează acele fișiere ca intrare (sau ieșire, dar nu în mod obișnuit), fiecare dintre aceste fișiere poate fi o țeavă numită sau un pseudo-fișier / dev / fd furnizat transparent prin substituirea procesului:
$ sort -m <(command1) <(command2) <(command3)
va „transmite” ieșirea celor trei comenzi pentru sortare, deoarece sortarea poate lua o listă de fișiere de intrare pe linia de comandă.
Comentarii
- IIRC sintaxa < (comandă) este o caracteristică numai bash.
- @Philomath: ‘ este în Și ZSH.
- Ei bine, ZSH are totul … (sau cel puțin încearcă).
- @Philomath: Cum este implementată substituirea proceselor în alte shell-uri?
- @Philomath
<()
, la fel ca multe caracteristici de shell avansate, a fost inițial o caracteristică ksh și a fost adoptată de bash și zsh.psub
este în mod specific o caracteristică de pește, nicio legătură cu POSIX.
Răspuns
Trebuie remarcat faptul că înlocuirea procesului nu se limitează la forma <(command)
, care utilizează ieșirea command
ca fişier. Poate fi sub forma >(command)
care alimentează un fișier ca intrare la command
. Acest lucru este menționat și în citatul manualului bash din răspunsul lui @enzotib.
Pentru exemplul date | cat
de mai sus, o comandă care folosește procesul de substituire a formați >(command)
pentru a obține același efect,
date > >(cat)
Rețineți că >
înainte ca >(cat)
să fie necesar. Acest lucru poate fi din nou ilustrat clar de echo
ca în răspunsul lui @Caleb.
$ echo >(cat) /dev/fd/63
Deci, fără >
suplimentar, date >(cat)
ar fi la fel ca date /dev/fd/63
care va imprima un mesaj către stderr.
Să presupunem că aveți un program care ia doar nume de fișiere ca parametri și nu procesează stdin
sau stdout
.Voi folosi scriptul simplificat psub.sh
pentru a ilustra acest lucru. Conținutul psub.sh
este
#!/bin/bash [ -e "$1" -a -e "$2" ] && awk "{print $1}" "$1" > "$2"
Practic, testează că ambele argumente ale sale sunt fișiere (nu neapărat regulate fișiere) și dacă acesta este cazul, scrieți primul câmp al fiecărei linii a "$1"
la "$2"
folosind awk. Apoi, o comandă care combină toate cele menționate până acum este,
./psub.sh <(printf "a a\nc c\nb b") >(sort)
Aceasta se va imprima
a b c
și este echivalent cu
printf "a a\nc c\nb b" | awk "{print $1}" | sort
dar următoarele nu vor funcționa și trebuie să folosim substituirea procesului aici,
printf "a a\nc c\nb b" | ./psub.sh | sort
sau forma sa echivalentă
printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort
Dacă se citește și ./psub.sh
stdin
în afară de ceea ce este menționat mai sus, atunci o astfel de formă echivalentă nu există și, în acest caz, nu putem folosi nimic în locul substituirii procesului (desigur, puteți utiliza și un numit fișier pipe sau temp, dar asta este o altă poveste).
[[ -p <(date) ]] && echo true
. Aceasta producetrue
când îl rulez cu bash 4.4 sau 3.2.