Înlocuirea procesului și conducta

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

  • Dacă înțeles corect, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 spune că înlocuirea procesului creează fișiere temporare, nu denumite conducte. Din câte știu numit nu creați fișiere temporare. Scrierea pe țeavă nu implică niciodată scrierea pe disc: stackoverflow.com/a/6977599/788700
  • Știu că acest răspuns este legitim ‘ deoarece folosește cuvântul grok : D
  • @Adobe puteți confirma dacă procesul de substituire temporară a fișierului produs este o țeavă numită cu: [[ -p <(date) ]] && echo true. Aceasta produce true când îl rulez cu bash 4.4 sau 3.2.

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 care cd 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).

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *