MegaLab.it
Stampa Articolo
Aperiodico gratuito di informatica
 
20100315081905_91510602_20100315081846_1745368878_pipe_spotlight.png

Bash: pipe e redirezioni

a cura di masterz3d
07/05/2010 - articolo
Linux & Open Source - Pipe e redirezioni sono tra gli strumenti fondamentali di utilizzo della shell Bash, e costituiscono parte del bagaglio di conoscenze di base che un utente della console testuale di Linux deve avere.

L'incredibile versatilità di Bash viene dimostrata da due caratteristiche estremamente utili della shell: la possibilità di creare pipe o pipeline (dall'inglese "pipe", ovvero "tubo", dove una "pipeline" è una riga di comando fatta di uno o più pipe), che sono come dei vasi comunicanti dove il fluido in uscita da un vaso viene mandato all'interno del vaso successivo per un'ulteriore lavorazione, e le redirezioni, ovvero delle "corsie preferenziali" su cui vengono indirizzati dei flussi di dati, per poi sfociare in delle uscite particolari chiamate descrittori di file, o file descriptors. I file descriptors giocano un ruolo cruciale in entrambe queste funzioni, e prima di andare avanti dobbiamo chiarire che cosa sono.

Descrittori di file

Ricordate gli esempi di file descriptor evidenziati nella guida a dd?

I file descriptors possono essere visti sia come corsie preferenziali che come tubi, attraverso cui passano dei flussi di dati diretti verso un altro descrittore di file, che a sua volta sfocia in un programma o in un file "concreto".

I descrittori di file interagiscono con i programmi di origine o di destinazione così come i segnali elettronici vengono elaborati da più circuiti integrati in cascata, cosicchè all'uscita dell'intero sistema ci sia il segnale filtrato e lavorato, pronto per essere usato nel modo che ci eravamo prefissi.

I file descriptors (o fd [1]) più usati e comuni sono tre:

Attraverso questi tre descrittori si gioca qualcosa come il 99% dei flussi di entrata e uscita dei programmi UNIX nelle pipe e nelle redirezioni. [3]

Un modo per accedere al contenuto dei descrittori di un programma in esecuzione è entrare nella directory

/proc/$(pidof <programma>)/fd

Dove <programma> è il nome del programma in esecuzione (possiamo chiamarlo anche processo) di cui vogliamo conoscere i descrittori di file. [4]

Accederete ad una lista di link simbolici che fanno riferimento al terminale virtuale in cui è in esecuzione il processo.

Esempio: controlliamo i dati di dd

Un modo semplice di controllare che cosa passa "attraverso" un processo di dd è chiamarlo in causa con questa invocazione:

dd if=/dev/urandom of=/dev/null bs=4k

Che, ricordiamo (da questo articolo), è equivalente ad eseguire

cat /dev/urandom | dd bs=4k >/dev/null

cat /dev/urandom è il nostro stdin e /dev/null è ciò che ci aspetta in stdout.

Con dd in esecuzione in background entriamo nella directory dei suoi descrittori:

cd /proc/$(pidof dd)/fd

E, uno alla volta, controlliamoli:

cat 0

Vedrete una sequenza praticamente infinita di caratteri casuali, che è la chiamata di cat per la lettura di /dev/urandom e il reindirizzamento in fd0 di dd. Potete premere Ctrl+C in qualsiasi momento per terminare la lettura della sequenza di caratteri casuali. [5]

cat 1

È /dev/null, che è un particolare file in cui non esiste nulla e viene usato come "buco nero", in cui ciò che viene gettato non può più essere recuperato. Infatti, appena tenterete di analizzare fd1 vedrete solo un nuovo prompt di Bash, segno che /dev/null è qualcosa di vuoto e non contiene... nulla.

Esempio: semplice uso di grep

grep è un programma che filtra un file in entrata (dalla sua stessa invocazione o da stdin, avete capito qual è la differenza tra file in entrata e file in uscita?) secondo dei parametri decisi sulla riga di comando, che la maggior parte delle volte corrispondono ad una semplice stringa. Quindi esaminiamo il suo uso più comune:

grep "stringa" <file>

grep guarda in <file> se esiste la stringa "stringa" e ne visualizza le corrispondenze a video. Ma ormai sappiamo che grep può essere usato anche con stdin, e se abbiamo un file <file> che leggiamo con cat la riga si trasforma così:

cat <file> | grep "stringa"

Che è esattamente equivalente all'invocazione precedente. Ma mettiamo il caso che nel sistema non sia installato zgrep e disponiamo solamente di zcat per leggere un file di testo compresso con gzip. Allora dobbiamo per forza di cose usare questa riga di comando:

zcat <file.gz> | grep "stringa"

E possiamo sapere se ci sono corrispondenze senza estrarre il file (o i file) che sono stati compressi nell'archivio <file.gz>. Ma sappiamo anche che, se stdin di grep è equivalente a /dev/fd/0 di se stesso ed è uguale a stdout di zcat, e grep accetta un file come parametro, possiamo modificare la riga precedente così:

zcat <file.gz> | grep "stringa" /dev/fd/0

Dove il /dev/fd/0 che abbiamo compreso nel comando è lo stdin del processo chiamante, ovvero grep. Si può anche scrivere

zcat <file.gz> | grep "stringa" /dev/stdin

Poiché /dev/stdin è un symlink a /dev/fd/0. Ciò vale per tutti i descrittori. [6]

Riassumendo

/dev/fd/0, /dev/fd/1, /dev/fd/2 e /dev/stdin, /dev/stdout, /dev/stderr rispettivamente sono riferiti tutti al processo che li chiama, e hanno effetto solo se il processo chiamante è oggetto di pipe o redirezione. Le directory in /proc si riferiscono a processi già in esecuzione, e ha senso usarli per controllare lo stato di un demone o di un processo in background.

Generalmente parlando, i file descriptors sono come le connessioni neurali, attraverso cui passano i segnali (o flussi di dati) che hanno come destinazione uno o più neuroni per una successiva elaborazione.

Analisi di un comando 1

In Linux si usa molto spesso un programma di compressione, gzip, che accetta come stdin un flusso di dati, e il suo utilizzo (dal punto di vista dei descrittori) è simile a tar:

cat <file> | gzip -9 >file.gz

In questa semplice riga di comando è racchiuso l'intero significato di questo articolo, c'è un pipe e una redirezione. Analizziamoli uno alla volta.

cat <file> | gzip -9

Ricordando ciò che abbiamo detto sui file descriptors, in questo caso l'output di cat (ovvero il contenuto di <file>) viene esposto in stdout e indirizzato nello stdin di gzip attraverso il simbolo di pipe (|), che dà il nome alla funzione, per la compressione a livello massimo con l'opzione -9.

>file.gz

È una redirezione: per default gzip genera l'archivio compresso nel suo descrittore 1, ovvero l'output standard (non a caso è chiamato stdout). Quindi usiamo una redirezione, con il carattere >, per riversare il flusso di dati in output in un file su disco. [7] [11]

Analisi di un comando 2

Questo è più complesso, e lo analizzeremo passo per passo prima di concludere a che cosa serve.

Tralasciando il costrutto for..do..done, qui abbiamo tutto: pipe e redirezioni.

Per ogni modulo module (ovvero, file che hanno estensione .ko) nella directory dei moduli del kernel corrente, ne estraiamo il nome con

echo $(basename $module)

Che viene poi mandato come standard input di sed, dove ne filtriamo l'estensione con

sed 's/\.ko//g'

E l'output di sed, ovvero il nome del modulo con l'estensione rimossa, viene accodato nel file modules.list della directory corrente. Potete poi vedere i contenuti di modules.list con un semplice

cat modules.list [8] [9] [10].

Appendice: "here documents"

Gli "here documents" sono tipi di redirezioni molto particolari, e sono molto utili se dovete scrivere un semplice file di testo e non avete a disposizione un editor ASCII: vi basta cat.

Gli "here documents" permettono di far immettere righe di testo in un file direttamente dalla shell di comando, fino a che non viene scritta una stringa, case-sensitive, che corrisponda alla stringa definita come "here document".

La struttura è molto semplice:

cat > file <<STRINGA

Nell'esempio riportato potremo scrivere quello che vorremo in file, fino a che nelle righe in input non comparirà la stringa STRINGA.

Here_document_001.png

In seguito ci basterà leggere il file file direttamente con cat per ottenere il contenuto, che è ciò che abbiamo scritto prima, tranne la stringa finale:

Here_document_002.png

Concludendo...

Pipe (|) e redirezioni (>, >>, <, <<) sono solo due tra le molte funzioni della shell, e persino gli utenti GNU/Linux più navigati non hanno idea di tutti i suoi potenziali usi. Questa intende solo essere una breve guida nella descrizione sommaria delle caratteristiche più comuni.

Note

Nota [1]: i descrittori di file non devono essere confusi con le unità floppy (/dev/fd0, /dev/fd1...) in quanto hanno la loro directory esclusiva in /dev (dev/fd).

Nota [2]: i loro nomi sono puramente riferiti all'utilizzo: stdin è il flusso di dati in entrata in un programma dallo stdout del processo precedente nella pipeline; stdout si riferisce ai flussi di dati in uscita, stderr raggruppa i messaggi d'errore di un programma.

Nota [3]: solitamente tutti i programmi UNIX hanno un descrittore in entrata (stdin) e due descrittori in uscita (stdout e stderr).

Nota [4]: è probabile che il processo in esame abbia più di un PID (Process ID, identificatore di processo), quindi se ne può prendere in considerazione uno solo con

cd /proc/$(pidof <programma> | cut -d' ' -f1)/fd.

Nota [5]: casomai doveste subire gli effetti del garbled terminal vi basterà invocare reset da riga di comando.

Nota [6]: potete trovare un ulteriore esempio di pipe in questo articolo.

Nota [7]: l'output di gzip è un archivio gzip, quindi è sensato dargli un'estensione appropriata. Ciononostante il tipo di file non è determinato dall'estensione, ma dal suo contenuto, e potete dargli l'estensione che più vi piace, ma sappiate che programmi come browser GUI di cartelle riconoscono i tipi di file dall'estensione.

Nota [8]: la stessa cosa si sarebbe potuta fare con un'unica invocazione di basename, ovvero:

Così facendo basename filtra automaticamente l'estensione, mentre sed si limita a prendere tutte le stringhe (in gergo pattern) che corrispondono a .ko e le cancella. Questo può causare la cancellazione di pattern che non appartengono all'estensione. Tuttavia, usando solo basename non avremmo avuto il pipe che ci serviva per la dimostrazione...

Nota [9]: il simbolo di redirezione riversa l'output in un file:

Il simbolo di redirezione >> (due "maggiore" in fila) riversa l'output nello stesso modo, ma se il file esiste il flusso di dati proveniente dall'ultimo programma viene accodato ai contenuti già presenti.

Nota [10]: per chiarimenti sulla struttura di $(...) fate riferimento a questo articolo.

Nota [11]: è possibile reindirizzare il contenuto di un file descriptor in un altro, usando il costrutto N>&M, dove N è il descrittore da copiare e M è il descrittore di destinazione.

Il costrutto copia il contenuto del descrittore N nel descrittore M: così facendo è possibile copiare i messaggi da stderr (descrittore 2) in stdout (descrittore 1) di un programma, salvandone anche i messaggi d'errore (che comunemente viaggiano in stderr) in un file con una redirezione:

programma 2>&1 >file.log

L'output standard e i messaggi d'errore vengono concatenati e riversati nel file file.log per una successiva consultazione. Ciò è molto utile per confrontare errori particolari o per fare debug di un programma.

In particolare, esiste una differenza tra N>&M e N&>M, ma è molto sottile ed è essenzialmente irrilevante per un utente a cui non interessino i particolari delle redirezioni.

MegaLab.it rispetta la tua privacy. Per esercitare i tuoi diritti scrivi a: privacy@megalab.it .

Copyright 2008 MegaLab.it - Tutti i diritti sono riservati