Sostituzione del processo e pipe


86

Mi chiedevo come capire quanto segue:

Il piping dello stdout di un comando nello stdin di un altro è una tecnica potente. Ma cosa succede se è necessario eseguire il pipe dello stdout di più comandi? È qui che entra in gioco la sostituzione del processo.

In altre parole, la sostituzione di processo può fare qualunque cosa pipe possa fare?

Cosa può fare la sostituzione di processo, ma pipe no?

Risposte:


134

Un buon modo per individuare la differenza tra loro è fare un piccolo esperimento sulla riga di comando. Nonostante la somiglianza visiva nell'uso del <personaggio, fa qualcosa di molto diverso da un reindirizzamento o una pipe.

Usiamo il datecomando per i test.

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

Questo è un esempio inutile ma mostra che ha cataccettato l'output di datesu STDIN e lo ha sputato indietro. Gli stessi risultati possono essere ottenuti sostituendo il processo:

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

Tuttavia, ciò che è appena accaduto dietro le quinte era diverso. Invece di ricevere un flusso STDIN, è catstato effettivamente passato il nome di un file che doveva aprire e leggere. Puoi vedere questo passaggio usando echoinvece di cat.

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

Quando cat ha ricevuto il nome del file, ha letto il contenuto del file per noi. D'altra parte, l'eco ci ha appena mostrato il nome del file che è stato passato. Questa differenza diventa più evidente se aggiungi più sostituzioni:

$ 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

È possibile combinare la sostituzione di processo (che genera un file) e il reindirizzamento di input (che collega un file a STDIN):

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

Sembra praticamente lo stesso, ma questa volta a cat è stato passato il flusso STDIN anziché un nome di file. Puoi vederlo provandolo con l'eco:

$ echo < <(date)
<blank>

Poiché l'eco non legge STDIN e non è stato passato alcun argomento, non si ottiene nulla.

Pipes e reindirizzamenti di input spostano il contenuto sul flusso STDIN. La sostituzione del processo esegue i comandi, salva il loro output in un file temporaneo speciale e quindi passa il nome del file al posto del comando. Qualunque comando tu stia utilizzando, lo considera come un nome file. Si noti che il file creato non è un file normale ma una pipa denominata che viene rimossa automaticamente quando non è più necessaria.


Se ho capito correttamente , tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 afferma che la sostituzione del processo crea file temporanei, non named pipe. Per quanto ne so nominato non creare file temporanei. Scrivere sulla pipe non comporta mai la scrittura su disco: stackoverflow.com/a/6977599/788700
Adobe

So che questa risposta è legittima perché usa la parola grok : D
aqn

2
@Adobe è possibile confermare se il processo di sostituzione di file temporaneo produce è una named pipe con: [[ -p <(date) ]] && echo true. Questo produce truequando lo eseguo con bash 4.4 o 3.2.
De Novo,

24

Dovrei supporre che tu stia parlando basho qualche altra shell avanzata, perché la shell posix non ha una sostituzione di processo .

bash rapporti sulla pagina del manuale:

Sostituzione del
processo La sostituzione del processo è supportata su sistemi che supportano named pipe (FIFO) o il metodo / dev / fd di denominazione dei file aperti. Prende la forma di <(elenco) o> (elenco). L'elenco dei processi viene eseguito con i relativi input o output collegati a un FIFO o ad alcuni file in / dev / fd. Il nome di questo file viene passato come argomento al comando corrente come risultato dell'espansione. Se viene utilizzato il modulo> (elenco), la scrittura nel file fornirà l'input per l'elenco. Se viene utilizzato il modulo <(elenco), il file passato come argomento deve essere letto per ottenere l'output dell'elenco.

Se disponibile, la sostituzione del processo viene eseguita contemporaneamente all'espansione dei parametri e delle variabili, alla sostituzione dei comandi e all'espansione aritmetica.

In altre parole, e da un punto di vista pratico, puoi usare un'espressione come la seguente

<(commands)

come nome file per altri comandi che richiedono un file come parametro. Oppure puoi usare il reindirizzamento per tale file:

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

Tornando alla tua domanda, mi sembra che la sostituzione del processo e le pipe non abbiano molto in comune.

Se si desidera reindirizzare in sequenza l'output di più comandi, è possibile utilizzare uno dei seguenti moduli:

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

ma puoi anche usare il reindirizzamento sulla sostituzione del processo

command3 < <(command1; command2)

infine, se command3accetta un parametro file (in sostituzione di stdin)

command3 <(command1; command2)

quindi <() e <<() hanno lo stesso effetto, giusto?
solfish

@solfish: non esatto: il fuoco può essere usato ovunque ci si aspetti un nome file, il secondo è un reindirizzamento di input per quel nome
enzotib

23

Ecco tre cose che puoi fare con la sostituzione del processo che altrimenti sarebbero impossibili.

Ingressi di processo multipli

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

Semplicemente non c'è modo di farlo con le pipe.

Preservare STDIN

Di 'che hai il seguente:

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

E vuoi eseguirlo direttamente. Quanto segue fallisce miseramente. Bash sta già usando STDIN per leggere lo script, quindi altri input sono impossibili.

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

Ma in questo modo funziona perfettamente.

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

Sostituzione del processo in uscita

Si noti inoltre che la sostituzione del processo funziona anche nell'altro modo. Quindi puoi fare qualcosa del genere:

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

Questo è un po 'un esempio contorto, ma invia stdout a /dev/null, mentre lo piping stderr a uno script sed per estrarre i nomi dei file per i quali è stato visualizzato un errore "Autorizzazione negata" e quindi invia QUESTI risultati a un file.

Si noti che il primo comando e il reindirizzamento stdout sono tra parentesi ( subshell ) in modo che solo il risultato di quel comando venga inviato /dev/nulle non si scherzi con il resto della riga.


Vale la pena notare che nel diffesempio si potrebbe desiderare di preoccuparsi del caso in cui il cdpotrebbe non riuscire: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk,

"while piping stderr": non è il punto che non si tratta di piping, ma passa attraverso un file FIFO?
Gauthier,

@Gauthier no; il comando viene sostituito non con un fifo ma con un riferimento al descrittore di file. Quindi "echo <(echo)" dovrebbe produrre qualcosa come "/ dev / fd / 63", che è un dispositivo a caratteri speciali che legge o scrive dal numero FD 63.
tylerl,

10

Se un comando accetta un elenco di file come argomenti e li elabora come input (o output, ma non comunemente), ognuno di questi file può essere una pipe denominata o / pseudo-file / dev / fd fornito in modo trasparente dall'abbonamento al processo:

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

Questo "convoglia" l'output dei tre comandi da ordinare, poiché l'ordinamento può prendere un elenco di file di input sulla riga di comando.


1
IIRC la sintassi <(comando) è una funzione solo bash.
Philomath,

@Philomath: è anche in ZSH.
Caleb,

Bene, ZSH ha tutto ... (o almeno ci prova).
Philomath,

@Philomath: come viene implementata la sostituzione di processo in altre shell?
Camh,

4
@Philomath <(), come molte funzionalità avanzate della shell, era originariamente una funzionalità ksh ed è stata adottata da bash e zsh. psubè specificamente una funzione di pesce, nulla a che fare con POSIX.
Gilles,

3

Va notato che la sostituzione del processo non è limitata al modulo <(command), che utilizza l'output di commandcome file. Può essere nella forma >(command)che alimenta anche un file come input command. Questo è anche menzionato nella citazione del manuale di bash nella risposta di @ enzotib.

Per l' date | catesempio di cui sopra, un comando che utilizza la sostituzione processo di forma >(command)per ottenere lo stesso effetto sarebbe,

date > >(cat)

Si noti che >prima >(cat)è necessario. Questo può essere nuovamente illustrato chiaramente echocome nella risposta di @ Caleb.

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

Quindi, senza extra >, date >(cat)sarebbe lo stesso date /dev/fd/63che stamperà un messaggio a stderr.

Supponiamo di avere un programma che accetta solo nomi di file come parametri e che non elabora stdino stdout. Userò lo script semplificato psub.shper illustrare questo. Il contenuto di psub.shè

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

Fondamentalmente, verifica che entrambe le argomentazioni sono file (file non necessariamente regolari) e se questo è il caso, scrivere il primo campo di ogni riga di "$1"per "$2"usare awk. Quindi, un comando che combina tutto quanto menzionato finora è,

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

Questo stamperà

a
b
c

ed è equivalente a

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

ma quanto segue non funzionerà e qui dobbiamo usare la sostituzione del processo,

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

o la sua forma equivalente

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

Se ./psub.shsi legge anche stdinoltre a quanto menzionato sopra, allora non esiste un modulo simile e in tal caso non c'è nulla che possiamo usare al posto della sostituzione del processo (ovviamente puoi anche usare un pipe chiamato o un file temporaneo, ma questo è un altro storia).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.