Processbyte och rör

Jag undrar hur man ska förstå följande:

Att lägga ett kommandos stdout i en annans stdin är en kraftfull teknik. Men vad händer om du behöver pipa stdout av flera kommandon? Det är här processersättning kommer in.

Med andra ord, kan processersättning göra vad rör kan göra?

Vad kan processersättning göra, men röret kan inte?

Svar

Ett bra sätt att groka skillnaden mellan dem är att göra lite experiment på kommandoraden. Trots den visuella likheten vid användning av < karaktären gör den något helt annat än en omdirigering eller pip.

Låt oss använda date kommando för testning.

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

Detta är ett meningslöst exempel men det visar att cat accepterade utdata från date på STDIN och spottade ut det igen. Samma resultat kan uppnås genom processbyte:

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

Men vad som just hände bakom kulisserna var annorlunda. I stället för att få en STDIN-ström fick cat faktiskt namnet på en fil som den behövde för att öppna och läs. Du kan se detta steg genom att använda echo istället för cat.

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

När katten fick filnamnet läste den filens innehåll för oss. Å andra sidan visade echo oss bara filens namn som den skickades. Denna skillnad blir tydligare om du lägger till fler ersättningar:

$ 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 

Det är möjligt att kombinera processersättning (som genererar en fil) och inmatningsomdirigering (som ansluter en fil till STDIN):

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

Det ser ungefär samma ut men den här gången passerade katten STDIN-ström istället för ett filnamn. Du kan se detta genom att prova det med eko:

$ echo < <(date) <blank> 

Eftersom ekot inte läser STDIN och inget argument skickades, vi får ingenting.

Rör och inmatningsomdirigeringar skjuter innehåll till STDIN-strömmen. Processbyte kör kommandona, sparar deras utdata till en speciell tillfällig fil och skickar sedan filnamnet istället för kommandot. Oavsett vilket kommando du använder behandlas det som ett filnamn. Observera att den skapade filen inte är en vanlig fil utan ett namngivet rör som tas bort automatiskt när den inte längre behövs.

Kommentarer

  • Om jag förstås korrekt, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 säger att processersättning skapar tillfälliga filer, inte namngivna rör. Så vitt jag vet namnger inte skapa tillfälliga filer. Att skriva till röret innebär aldrig att man skriver till disk: stackoverflow.com/a/6977599/788700
  • Jag vet att svaret är legitimt ’ eftersom det använder ordet grok : D
  • @Adobe kan du bekräfta om den temporära filprocessersättningen producerar är ett namngivet rör med: [[ -p <(date) ]] && echo true. Detta ger true när jag kör det med bash 4.4 eller 3.2.

Svar

Här är tre saker du kan göra med processbyte som annars är omöjliga.

Flera processingångar

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

Där det är helt enkelt inget sätt att göra detta med rör.

Bevara STDIN

Säg att du har följande:

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

Och du vill köra den direkt. Följande misslyckas olyckligt. Bash använder redan STDIN för att läsa skriptet, så andra inmatningar är omöjliga.

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

Men det här sättet fungerar perfekt.

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

Ersättning av utgående process

Observera också att processersättning fungerar också på andra sätt. Så du kan göra något så här:

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

Det är lite av ett invecklat exempel, men det skickar stdout till /dev/null, medan du rör stderr till ett sed-skript för att extrahera namnen på filerna som en ” Tillåtelse nekad ” -felet visades och skickar sedan DE-resultaten till en fil.

Observera att det första kommandot och stdout omdirigeringen är inom parentes ( subshell ) så att endast resultatet av DET kommandot skickas till /dev/null och det inte rör sig med resten av raden.

Kommentarer

  • Det ’ är värt att notera att i diff exempel kanske du vill bry dig om fallet där cd kanske misslyckas: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
  • ” medan pipa stderr ”: är inte ’ t påpeka att det här är inte piping, utan går igenom en fifo-fil?
  • @Gauthier no; kommandot ersätts inte med ett femtal utan med en hänvisning till filbeskrivaren. Så ” echo < (echo) ” ska ge något som ” / dev / fd / 63 ”, som är en specialteckenanordning som läser eller skriver från FD-nummer 63.

Svar

Jag antar att du pratar om bash eller något annat avancerat skal, eftersom posix skalet har inte processbyte .

bash manuella sidrapporter:

Processbyte
Processersättning stöds på system som stöder namngivna rör (FIFO) eller / dev / fd-metoden för att namnge öppna filer. Det har formen av < (lista) eller> (lista). Processlistan körs med dess ingång eller utgång ansluten till en FIFO eller någon fil i / dev / fd. Namnet på den här filen skickas som ett argument till det aktuella kommandot som ett resultat av utvidgningen. Om formuläret> (lista) används kommer skrivning till filen att ge information för listan. Om formuläret < (lista) används, ska filen som skickas som ett argument läsas för att få utdata från listan.

När det är tillgängligt, processbyte utförs samtidigt med parameter- och variabelutvidgning, kommandosubstitution och aritmetisk expansion.

Med andra ord och ur en praktisk synvinkel kan du använda ett uttryck som följande

<(commands) 

som filnamn för andra kommandon som kräver en fil som parameter. Eller så kan du använda omdirigering för en sådan fil:

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

Om du återgår till din fråga verkar det som om processersättning och rör inte har mycket gemensamt.

Om du vill röra i följd utdata från flera kommandon kan du använda någon av följande former:

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

men du kan också använda omdirigering vid processersättning

command3 < <(command1; command2) 

slutligen, om command3 accepterar en filparameter (i stället för stdin )

command3 <(command1; command2) 

Kommentarer

  • så < ( ) och < < () har samma effekt, eller hur?
  • @solfish: inte exacllty: branden kan användas varhelst ett filnamn förväntas, det andra är en ingångs omdirigering för det filnamnet

Svar

Om en kommandot tar en lista över filer som argument och bearbetar dessa filer som inmatning (eller utdata, men inte vanligt), kan var och en av dessa filer vara en namngiven pipa eller / dev / fd pseudofil som tillhandahålls transparent genom processubstitution:

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

Detta kommer att ”pipa” utmatningen av de tre kommandona för att sortera, eftersom sorteringen kan ta en lista med inmatningsfiler på kommandoraden.

Kommentarer

  • IIRC < (kommando) syntax är en enda bash-funktion.
  • @Philomath: Den ’ s i ZSH också.
  • Tja, ZSH har allt … (eller försöker åtminstone).
  • @Philomath: Hur implementeras processersättning i andra skal?
  • @Philomath <(), som många avancerade skalfunktioner, var ursprungligen en ksh-funktion och antogs av bash och zsh. psub är specifikt en fiskfunktion, inget att göra med POSIX.

Svar

Det bör noteras att processersättning inte är begränsad till formen <(command), som använder utdata från command som en fil. Det kan vara i formen >(command) som också matar en fil som ingång till command. Detta nämns också i citatet av bash manual i @enzotibs svar.

För date | cat exempel ovan, ett kommando som använder processersättning av form >(command) för att uppnå samma effekt skulle vara,

date > >(cat) 

Observera att > innan >(cat) är nödvändig. Detta kan återigen tydligt illustreras med echo som i @Calebs svar.

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

Så utan den extra > skulle date >(cat) vara samma som date /dev/fd/63 som skriver ut ett meddelande till stderr.

Antag att du har ett program som bara tar filnamn som parametrar och inte bearbetar stdin eller stdout.Jag kommer att använda det förenklade skriptet psub.sh för att illustrera detta. Innehållet i psub.sh är

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

I grund och botten testar det att båda dess argument är filer (inte nödvändigtvis vanliga filer) och om så är fallet, skriv det första fältet på varje rad i "$1" till "$2" med hjälp av awk. Sedan är ett kommando som kombinerar allt som nämnts hittills,

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

Detta kommer att skrivas ut

a b c 

och motsvarar

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

men följande fungerar inte, och vi måste använda processersättning här,

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

eller motsvarande form

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

Om ./psub.sh också läser stdin förutom vad som nämns ovan, så existerar inte en sådan motsvarande form, och i så fall finns det inget vi kan använda istället för processersättning (naturligtvis kan du också använda en namnges rör- eller tempfil, men det är en annan historia).

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *