it-swarm.it

Sostituzione del processo e pipe

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?

89
Tim

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

Usiamo il comando date per i test.

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

Questo è un esempio inutile, ma mostra che cat ha accettato l'output di date su 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, cat è stato effettivamente passato il nome di un file che doveva aprire e leggere. Puoi vedere questo passaggio usando echo invece 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 otteniamo nulla.

Tubi e reindirizzamenti di input spostano il contenuto sul flusso STDIN. La sostituzione del processo esegue i comandi, salva l'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.

140
Caleb

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, durante il piping stderr a uno script sed per estrarre i nomi dei file per i quali è stato visualizzato l'errore "Autorizzazione negata", quindi invia QUESTI risultati a un file.

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

26
tylerl

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

bash rapporti sulle pagine del manuale:

Sostituzione del processo
La sostituzione del processo è supportata su sistemi che supportano named pipe (FIFO) o il metodo/dev/fd di denominazione di file aperti. Prende la forma di <(elenco) o> (elenco). L'elenco dei processi viene eseguito con l'input o l'output connesso a un FIFO o qualche file in/dev/fd. Il nome di questo file viene passato come argomento al comando corrente come risultato di l'espansione Se si utilizza il modulo> (elenco), la scrittura nel file fornirà input per l'elenco. Se si utilizza 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 command3 accetta un parametro file (in sostituzione di stdin)

command3 <(command1; command2)
26
enzotib

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.

10
camh

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

Per l'esempio date | cat Sopra, un comando che utilizza la sostituzione di processo del form >(command) per ottenere lo stesso effetto sarebbe,

date > >(cat)

Si noti che > Prima di >(cat) è necessario. Questo può essere nuovamente illustrato chiaramente da echo come nella risposta di @ Caleb.

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

Quindi, senza l'aggiunta di >, date >(cat) sarebbe uguale a date /dev/fd/63 Che stamperà un messaggio su stderr.

Supponiamo di avere un programma che accetta solo i nomi dei file come parametri e non elabora stdin o stdout. Userò lo script semplificato psub.sh Per illustrare questo. Il contenuto di psub.sh È

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

Fondamentalmente, verifica che entrambi i suoi argomenti siano file (non necessariamente file regolari) e, in questo caso, scrivi il primo campo di ogni riga di "$1" Su "$2" Usando 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.sh Legge anche stdin oltre a quanto menzionato sopra, allora non esiste una forma simile equivalente, e in quel caso non c'è niente che possiamo usare al posto della sostituzione del processo (ovviamente puoi usa anche un file pipe o temp con nome, ma questa è un'altra storia).

3
Weijun Zhou