Prosessubstitusjon og rør

Jeg lurte på hvordan jeg skulle forstå følgende:

Å peke stdout av en kommando i stdin til en annen er en kraftig teknikk. Men hva om du trenger å peke ut stdout av flere kommandoer? Det er her prosessubstitusjon kommer inn.

Kan prosessubstitusjon med andre ord gjøre hva rør kan gjøre?

Hva kan prosessubstitusjon gjøre, men rør kan ikke?

Svar

En god måte å grok forskjellen mellom dem er å gjøre litt eksperimentering på kommandolinjen. Til tross for den visuelle likheten i bruk av < -tegnet, gjør den noe helt annet enn en omdirigering eller et rør.

La oss bruke date kommando for testing.

$ date | cat Thu Jul 21 12:39:18 EEST 2011 

Dette er et meningsløst eksempel, men det viser at cat aksepterte utdata fra date på STDIN og spyttet den ut igjen. De samme resultatene kan oppnås ved prosessubstitusjon:

$ cat <(date) Thu Jul 21 12:40:53 EEST 2011 

Men det som nettopp skjedde bak kulissene var annerledes. I stedet for å få en STDIN-strøm fikk cat faktisk navnet på en fil som den trengte for å åpne og les. Du kan se dette trinnet ved å bruke echo i stedet for cat.

$ echo <(date) /proc/self/fd/11 

Når katten mottok filnavnet, leste den filens innhold for oss. På den annen side viste ekko oss bare filens navn at den ble sendt. Denne forskjellen blir tydeligere hvis du legger til flere erstatninger:

$ 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 

It er mulig å kombinere prosessubstitusjon (som genererer en fil) og inndiretning (som kobler en fil til STDIN):

$ cat < <(date) Thu Jul 21 12:46:22 EEST 2011 

Det ser ganske likt ut, men denne gangen ble katten sendt STDIN-strøm i stedet for et filnavn. Du kan se dette ved å prøve det med ekko:

$ echo < <(date) <blank> 

Siden ekko ikke leser STDIN og ingen argumenter ble sendt, vi får ingenting.

Rør og viderekoblinger skyver innhold til STDIN-strømmen. Prosessubstitusjon kjører kommandoene, lagrer utdataene i en spesiell midlertidig fil og sender deretter filnavnet i stedet for kommandoen. Uansett hvilken kommando du bruker, behandles den som et filnavn. Merk at filen som er opprettet ikke er en vanlig fil, men et navngitt rør som fjernes automatisk når det ikke lenger er behov for det.

Kommentarer

  • Hvis jeg forstått rett, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 sier at prosessubstitusjon skaper midlertidige filer, ikke navngitte rør. Så vidt jeg vet navngitt, må du ikke opprette midlertidige filer. Å skrive til røret innebærer aldri å skrive til disk: stackoverflow.com/a/6977599/788700
  • Jeg vet at dette svaret er legitimt ‘ fordi det bruker ordet grok : D
  • @Adobe kan du bekrefte om den midlertidige filprosessubstitusjonen produserer er et navngitt rør med: [[ -p <(date) ]] && echo true. Dette gir true når jeg kjører det med bash 4.4 eller 3.2.

Svar

Her er tre ting du kan gjøre med prosessubstitusjon som ellers er umulig.

Flere prosessinnganger

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls) 

Der Det er ganske enkelt ingen måte å gjøre dette med rør.

Bevare STDIN

Si at du har følgende:

curl -o - http://example.com/script.sh #/bin/bash read LINE echo "You said ${LINE}!" 

Og du vil kjøre det direkte. Følgende mislykkes stort. Bash bruker allerede STDIN for å lese skriptet, så andre inndata er umulige.

curl -o - http://example.com/script.sh | bash 

Men denne måten fungerer perfekt.

bash <(curl -o - http://example.com/script.sh) 

Substitusjon for utgående prosess

Vær også oppmerksom på at prosessubstitusjon fungerer også den andre veien. Så du kan gjøre noe sånt som dette:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \ "/Permission denied/ s/.*\(\/proc.*\):.*/\1/p" > denied.txt ) 

Det er litt av et innviklet eksempel, men det sender stdout til /dev/null, mens du rør stderr til et sed-skript for å trekke ut navnene på filene som » Tillatelse nektet » feil ble vist, og sender deretter DENNE resultatene til en fil.

Merk at den første kommandoen og stdout omdirigering er i parentes ( subshell ) slik at bare resultatet av DEN kommandoen blir sendt til /dev/null og den ikke roter med resten av linjen.

Kommentarer

  • Det ‘ er verdt å merke seg at i diff eksempel vil du kanskje bry deg om saken der cd kan mislykkes: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
  • » mens rørledning «: er ikke ‘ t påpeke at dette er ikke piping, men går gjennom en fifo-fil?
  • @Gauthier no; kommandoen blir ikke erstattet med en fifo, men med en referanse til filbeskrivelsen. Så » ekko < (echo) » skal gi noe sånt som » / dev / fd / 63 «, som er et spesialtegn som leser eller skriver fra FD-nummer 63.

Svar

Jeg antar at du snakker om bash eller et annet avansert skall, fordi posix skallet har ikke prosessubstitusjon .

bash manuelle siderapporter:

Prosessubstitusjon
Prosessubstitusjon støttes på systemer som støtter navngitte rør (FIFOs) eller / dev / fd-metoden for å navngi åpne filer. Det tar form av < (liste) eller> (liste). Prosesslisten kjøres med inngang eller utgang koblet til en FIFO eller en fil i / dev / fd. Navnet på denne filen sendes som et argument til den gjeldende kommandoen som et resultat av utvidelsen. Hvis skjemaet> (liste) brukes, vil skriving til filen gi innspill til listen. Hvis < (liste) -skjemaet brukes, skal filen som sendes som et argument leses for å få utdata fra listen.

Når det er tilgjengelig, prosesserstatning utføres samtidig med parameter- og variabelutvidelse, kommandosubstitusjon og aritmetisk utvidelse.

Med andre ord, og fra et praktisk synspunkt kan du bruke et uttrykk som følgende

<(commands) 

som filnavn for andre kommandoer som krever en fil som parameter. Eller du kan bruke omdirigering for en slik fil:

while read line; do something; done < <(commands) 

Når du snur tilbake til spørsmålet ditt, ser det ut til at prosessubstitusjon og rør ikke har mye til felles.

Hvis du vil rense utdataene fra flere kommandoer i rekkefølge, kan du bruke en av følgende former:

(command1; command2) | command3 { command1; command2; } | command3 

men du kan også bruke omdirigering ved prosessubstitusjon

command3 < <(command1; command2) 

til slutt, hvis command3 godtar en filparameter (i erstatning for stdin )

command3 <(command1; command2) 

Kommentarer

  • så < ( ) og < < () gir samme effekt, ikke sant?
  • @solfish: ikke exacllty: brannen kan brukes uansett hvor det forventes et filnavn, det andre er en inndirigering for det filnavnet

Svar

Hvis en kommandoen tar en liste over filer som argumenter og behandler filene som input (eller output, men ikke ofte), kan hver av disse filene være et navngitt rør eller / dev / fd pseudofil som er gitt transparent av prosessubstitusjon:

$ sort -m <(command1) <(command2) <(command3) 

Dette vil «pipe» utgangen av de tre kommandoene som skal sorteres, ettersom sort kan ta en liste over inndatafiler på kommandolinjen.

Kommentarer

  • IIRC < (kommando) syntaksen er bare en bash-funksjon.
  • @Philomath: Den ‘ er i ZSH også.
  • Vel, ZSH har alt … (eller prøver i det minste).
  • @Philomath: Hvordan implementeres prosesserstatning i andre skall?
  • @Philomath <(), som mange avanserte skallfunksjoner, var opprinnelig en ksh-funksjon og ble adoptert av bash og zsh. psub er spesifikt en fiskefunksjon, ingenting med POSIX å gjøre.

Svar

Det bør bemerkes at substitusjon av prosesser ikke er begrenset til skjemaet <(command), som bruker utdata fra command fil. Det kan være i formen >(command) som også mater en fil som inngang til command. Dette er også nevnt i sitatet til bash manual i @enzotibs svar.

For date | cat eksempelet ovenfor, en kommando som bruker prosesserstatning av skjema >(command) for å oppnå samme effekt ville være,

date > >(cat) 

Merk at > før >(cat) er nødvendig. Dette kan igjen illustreres tydelig med echo som i @Calebs svar.

$ echo >(cat) /dev/fd/63 

Så uten den ekstra >, ville date >(cat) være samme som date /dev/fd/63 som vil skrive ut en melding til stderr.

Anta at du har et program som bare tar filnavn som parametere og ikke behandler stdin eller stdout.Jeg vil bruke det oversimpliserte skriptet psub.sh for å illustrere dette. Innholdet av psub.sh er

#!/bin/bash [ -e "$1" -a -e "$2" ] && awk "{print $1}" "$1" > "$2" 

I utgangspunktet tester det at begge argumentene er filer (ikke nødvendigvis vanlige filer), og hvis dette er tilfelle, skriv det første feltet i hver linje med "$1" til "$2" ved hjelp av awk. Så er en kommando som kombinerer alt som er nevnt så langt,

./psub.sh <(printf "a a\nc c\nb b") >(sort) 

Dette vil skrives ut

a b c 

og tilsvarer

printf "a a\nc c\nb b" | awk "{print $1}" | sort 

men følgende fungerer ikke, og vi må bruke prosessubstitusjon her,

printf "a a\nc c\nb b" | ./psub.sh | sort 

eller tilsvarende form

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort 

Hvis ./psub.sh også står stdin i tillegg til det som er nevnt ovenfor, eksisterer ikke en slik ekvivalent form, og i så fall er det ingenting vi kan bruke i stedet for prosesserstatning (selvfølgelig kan du også bruke en kalt pipe eller temp-fil, men det er en annen historie).

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *