Nota: la risposta riflette la mia comprensione di questi meccanismi aggiornati, accumulati durante la ricerca e la lettura delle risposte dai peer su questo sito e unix.stackexchange.com , e verranno aggiornati con il passare del tempo. Non esitate a porre domande o suggerire miglioramenti nei commenti. Ti suggerisco anche di provare a vedere come funzionano syscalls in shell con strace
comando. Inoltre, non fatevi intimidire dalla nozione di interni o syscalls - non dovete conoscerli o essere in grado di usarli per capire come shell fa le cose, ma sicuramente aiutano a capire.
TL; DR
|
le pipe non sono associate a una voce sul disco, quindi non hanno un numero di inode del filesystem del disco (ma hanno inode nel filesystem virtuale pipefs nello spazio del kernel), ma i reindirizzamenti spesso coinvolgono file, che hanno voci del disco e quindi hanno corrispondenti inode.
- le pipe non sono in
lseek()
grado, quindi i comandi non possono leggere alcuni dati e quindi riavvolgere indietro, ma quando si reindirizza con >
o di <
solito si tratta di un file che è lseek()
oggetto in grado, quindi i comandi possono navigare come preferiscono.
- i reindirizzamenti sono manipolazioni sui descrittori di file, che possono essere molti; le pipe hanno solo due descrittori di file: uno per il comando a sinistra e uno per il comando a destra
- il reindirizzamento su stream e pipe standard è entrambi bufferato.
- i tubi coinvolgono quasi sempre il biforcazione e quindi sono coinvolte coppie di processi; reindirizzamenti - non sempre, sebbene in entrambi i casi i descrittori di file risultanti siano ereditati da sottoprocessi.
- le pipe collegano sempre descrittori di file (una coppia), reindirizzamenti - utilizzare un nome percorso o descrittori di file.
- le pipe sono il metodo di comunicazione tra processi, mentre i reindirizzamenti sono solo manipolazioni su file aperti o oggetti simili a file
- entrambi utilizzano
dup2()
syscalls sotto il cofano per fornire copie dei descrittori di file, in cui si verifica il flusso effettivo di dati.
- i reindirizzamenti possono essere applicati "a livello globale" con il
exec
comando integrato (vedi questo e questo ), quindi se lo fai exec > output.txt
tutti i comandi scriveranno output.txt
da quel momento in poi. |
le pipe vengono applicate solo per il comando corrente (che significa comando semplice o subshell seq 5 | (head -n1; head -n2)
o comandi composti.
Quando il reindirizzamento viene eseguito sui file, cose come echo "TEST" > file
ed echo "TEST" >> file
entrambi usano open()
syscall su quel file ( vedi anche ) e ottengono da esso il descrittore di file per passarlo dup2()
. Pipes |
use only pipe()
e dup2()
syscall.
Per quanto riguarda i comandi in esecuzione, pipe e reindirizzamento non sono altro che descrittori di file - oggetti simili a file, su cui possono scrivere alla cieca o manipolarli internamente (il che può produrre comportamenti imprevisti; apt
ad esempio, tende a non scrivere nemmeno su stdout se sa che c'è il reindirizzamento).
introduzione
Per capire in che modo differiscono questi due meccanismi, è necessario comprendere le loro proprietà essenziali, la storia dietro i due e le loro radici nel linguaggio di programmazione C. In realtà, sapere quali file di descrittori sono, e come dup2()
e pipe()
sistema di chiamate di lavoro è essenziale, così come lseek()
. Shell è inteso come un modo per rendere astratti questi meccanismi per l'utente, ma scavare più a fondo dell'astrazione aiuta a comprendere la vera natura del comportamento della shell.
Le origini di reindirizzamenti e tubi
Secondo l'articolo di Dennis Ritche Prophetic Petroglyphs , le pipe sono nate da una nota interna del 1964 di Malcolm Douglas McIlroy , all'epoca in cui stavano lavorando al sistema operativo Multics . Citazione:
Per riassumere le mie più forti preoccupazioni:
- Dovremmo avere alcuni modi per collegare programmi come il tubo da giardino: avvitare un altro segmento quando diventa quando diventa necessario massaggiare i dati in un altro modo. Questo è anche il modo di IO.
Ciò che è evidente è che all'epoca i programmi erano in grado di scrivere su disco, tuttavia ciò era inefficiente se l'output era grande. Per citare la spiegazione di Brian Kernighan nel video di Unix Pipeline :
Innanzitutto, non devi scrivere un grosso programma di grandi dimensioni: hai programmi più piccoli esistenti che potrebbero già fare parte del lavoro ... Un altro è che è possibile che la quantità di dati che stai processando non si adatterebbe se l'hai archiviato in un file ... perché ricorda, siamo tornati nei giorni in cui i dischi su queste cose avevano, se sei stato fortunato, un Megabyte o due di dati ... Quindi la pipeline non ha mai dovuto istanziare l'intero output .
Quindi la differenza concettuale è evidente: le pipe sono un meccanismo per far dialogare i programmi. Reindirizzamenti: sono un modo di scrivere su file a livello base. In entrambi i casi, shell rende queste due cose facili, ma sotto il cofano c'è molto da fare.
Approfondimento: syscalls e meccanismi interni della shell
Iniziamo con la nozione di descrittore di file . I descrittori di file descrivono sostanzialmente un file aperto (che si tratti di un file su disco o in memoria o di un file anonimo), che è rappresentato da un numero intero. I due flussi di dati standard (stdin, stdout, stderr) sono rispettivamente descrittori di file 0,1 e 2. Da dove vengono ? Bene, nei comandi di shell i descrittori di file sono ereditati dalla loro shell madre. Ed è vero in generale per tutti i processi: il processo figlio eredita i descrittori di file dei genitori. Per i demoni è comune chiudere tutti i descrittori di file ereditati e / o reindirizzare verso altri luoghi.
Torna al reindirizzamento. Che cos'è veramente? È un meccanismo che dice alla shell di preparare i descrittori di file per il comando (perché i reindirizzamenti vengono effettuati dalla shell prima dell'esecuzione del comando) e li indirizzano dove l'utente ha suggerito. La definizione standard di reindirizzamento dell'output è
[n]>word
Che [n]
esiste il numero del descrittore di file. Quando lo fai echo "Something" > /dev/null
il numero 1 è implicito lì, e echo 2> /dev/null
.
Sotto il cofano questo viene fatto duplicando il descrittore di file tramite la dup2()
chiamata di sistema. Prendiamo df > /dev/null
. La shell creerà un processo figlio dove df
viene eseguita, ma prima si aprirà /dev/null
come descrittore di file n. 3 e dup2(3,1)
verrà emessa, il che crea una copia del descrittore di file 3 e la copia sarà 1. Sai come hai due file file1.txt
e file2.txt
e quando lo fai cp file1.txt file2.txt
avrai due stessi file, ma puoi manipolarli in modo indipendente? È un po 'la stessa cosa che succede qui. Spesso puoi vedere che prima di eseguirlo, lo bash
farà dup(1,10)
per creare un descrittore di file di copia n. 1 che è stdout
(e quella copia sarà fd # 10) per ripristinarlo in seguito. È importante notare che quando si considerano i comandi integrati(che fanno parte della shell stessa e non hanno file all'interno /bin
o altrove) o semplici comandi nella shell non interattiva , la shell non crea un processo figlio.
E poi abbiamo cose come [n]>&[m]
e [n]&<[m]
. Si tratta di duplicare i descrittori di file, che lo stesso meccanismo come dup2()
solo ora è nella sintassi della shell, comodamente disponibili per l'utente.
Una delle cose importanti da notare sul reindirizzamento è che il loro ordine non è fisso, ma è significativo per il modo in cui la shell interpreta ciò che l'utente desidera. Confronta quanto segue:
# Make copy of where fd 2 points , then redirect fd 2
$ ls -l /proc/self/fd/ 3>&2 2> /dev/null
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
lrwx------ 1 runner user 64 Sep 13 00:08 3 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/29/fd
# redirect fd #2 first, then clone it
$ ls -l /proc/self/fd/ 2> /dev/null 3>&2
total 0
lrwx------ 1 user user 64 Sep 13 00:08 0 -> /dev/pts/0
lrwx------ 1 user user 64 Sep 13 00:08 1 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:08 2 -> /dev/null
l-wx------ 1 user user 64 Sep 13 00:08 3 -> /dev/null
lr-x------ 1 user user 64 Sep 13 00:08 4 -> /proc/31/fd
L'uso pratico di questi negli script di shell può essere versatile:
e molti altri.
Impianto idraulico con pipe()
edup2()
Come si creano le pipe? Tramite pipe()
syscall , che prenderà come input un array (aka list) chiamato pipefd
di due elementi di tipo int
(intero). Quei due numeri interi sono descrittori di file. Il pipefd[0]
sarà la fine lettura della pipe e pipefd[1]
sarà la fine scrittura. Quindi df | grep 'foo'
, grep
otterrà una copia pipefd[0]
e df
riceverà una copia di pipefd[1]
. Ma come ? Certo, con la magia di dup2()
Syscall. Per df
esempio, nel nostro esempio, diciamo che pipefd[1]
ha il numero 4, quindi la shell farà un figlio, fallo dup2(4,1)
(ricordi il mio cp
esempio?), E poi execve()
eseguirà effettivamente df
. Naturalmente,df
erediterà il descrittore di file n. 1, ma non si accorgerà che non punta più al terminale, ma in realtà fd n. 4, che in realtà è l'estremità di scrittura della pipe. Naturalmente, accadrà la stessa cosa se grep 'foo'
non con un numero diverso di descrittori di file.
Ora, domanda interessante: potremmo creare pipe che reindirizzino anche fd # 2, non solo fd # 1? Sì, in effetti è quello che |&
fa in bash. Lo standard POSIX richiede il linguaggio dei comandi della shell per supportare la df 2>&1 | grep 'foo'
sintassi a tale scopo, ma lo bash
fa |&
anche.
La cosa importante da notare è che le pipe si occupano sempre dei descrittori di file. Esiste FIFO
o denominato pipe , che ha un nome file sul disco e ti consente di usarlo come file, ma si comporta come una pipe. Ma i |
tipi di pipe sono noti come pipe anonime: non hanno un nome file, perché sono in realtà solo due oggetti collegati tra loro. Il fatto che non abbiamo a che fare con i file comporta anche un'importante implicazione: le pipe non sono lseek()
'in grado. I file, sia in memoria che su disco, sono statici: i programmi possono usare lseek()
syscall per saltare al byte 120, quindi tornare al byte 10, quindi inoltrare fino alla fine. I tubi non sono statici: sono sequenziali e pertanto non è possibile riavvolgere i dati ottenuti con essilseek()
. Questo è ciò che rende consapevoli alcuni programmi se stanno leggendo da file o da pipe, e quindi possono apportare le necessarie regolazioni per prestazioni efficienti; in altre parole, prog
posso rilevare se lo faccio cat file.txt | prog
o prog < input.txt
. Il vero esempio di lavoro è la coda .
Le altre due proprietà molto interessanti delle pipe sono che hanno un buffer, che su Linux è di 4096 byte , e in realtà hanno un filesystem come definito nel codice sorgente di Linux ! Non sono semplicemente un oggetto per il trasferimento di dati, ma sono una struttura di dati! Infatti, poiché esiste un filesystem pipefs, che gestisce sia pipe sia FIFO, le pipe hanno un numero di inode sul rispettivo filesystem:
# Stdout of ls is wired to pipe
$ ls -l /proc/self/fd/ | cat
lrwx------ 1 user user 64 Sep 13 00:02 0 -> /dev/pts/0
l-wx------ 1 user user 64 Sep 13 00:02 1 -> pipe:[15655630]
lrwx------ 1 user user 64 Sep 13 00:02 2 -> /dev/pts/0
lr-x------ 1 user user 64 Sep 13 00:02 3 -> /proc/22/fd
# stdin of ls is wired to pipe
$ true | ls -l /proc/self/fd/0
lr-x------ 1 user user 64 Sep 13 03:58 /proc/self/fd/0 -> 'pipe:[54741]'
Su Linux le pipe sono unidirezionali, proprio come il reindirizzamento. Su alcune implementazioni simili a Unix - ci sono tubi bidirezionali. Sebbene con la magia degli script di shell, puoi creare pipe bidirezionali anche su Linux .
Guarda anche:
thing1 > temp_file && thing2 < temp_file
di fare di più con le pipe. Ma perché non riutilizzare l'>
operatore per farlo, ad esempiothing1 > thing2
per i comandithing1
ething2
? Perché un operatore extra|
?