Successivo: , Precedente: , Su: Funzionalità avanzate   [Contenuti][Indice]


12.3 Comunicazioni bidirezionali con un altro processo

Spesso è utile poter inviare dati a un programma separato che li elabori e in seguito leggere il risultato. Questo può essere sempre fatto con file temporanei:

# Scrivere i dati per l'elaborazione
filetemp = ("mieidati." PROCINFO["pid"])
while (non dipendente dai dati)
    print dati | ("sottoprogramma > " filetemp)
close("sottoprogramma > " filetemp)

# Legge il risultato, rimuove filetemp quando ha finito
while ((getline nuovidati < filetemp) > 0)
    elabora nuovidati secondo le esigenze
close(filetemp)
system("rm " filetemp)

Questo funziona, ma non è elegante. Tra le altre cose, richiede che il programma venga eseguito in una directory che non può essere condivisa tra gli utenti; per esempio, /tmp non può esserlo, poiché potrebbe accadere che un altro utente stia usando un file temporaneo con lo stesso nome.91

Comunque, con gawk, è possibile aprire una pipe bidirezionale verso un altro processo. Il secondo processo è chiamato coprocesso, poiché viene eseguito in parallelo con gawk. La connessione bidirezionale viene creata usando l’operatore ‘|&’ (preso in prestito dalla shell Korn, ksh):92

do {
    print dati |& "sottoprogramma"
    "sottoprogramma" |& getline risultato
} while (ci sono ancora dati da elaborare)
close("sottoprogramma")

La prima volta che viene eseguita un’operazione I/O usando l’operatore ‘|&’, gawk crea una pipeline bidirezionale verso un processo figlio che esegue l’altro programma. L’output creato con print o con printf viene scritto nello standard input del programma, e il contenuto dello standard output del programma può essere letto dal programma gawk usando getline. Come accade coi processi avviati con ‘|’, il sottoprogramma può essere un qualsiasi programma, o una pipeline di programmi, che può essere avviato dalla shell.

Ci sono alcune avvertenze da tenere presenti:

È possibile chiudere una pipe bidirezionale con un coprocesso solo in una direzione, fornendo un secondo argomento, "to" o "from", alla funzione close() (vedi la sezione Chiudere ridirezioni in input e in output). Queste stringhe dicono a gawk di chiudere la pipe, rispettivamente nella direzione che invia i dati al coprocesso e nella direzione che legge da esso.

Questo è particolarmente necessario per usare il programma di utilità di sistema sort come parte di un coprocesso; sort deve leggere tutti i dati di input prima di poter produrre un qualsiasi output. Il programma sort non riceve un’indicazione di fine-file (end-of-file) finché gawk non chiude l’estremità in scrittura della pipe.

Una volta terminata la scrittura dei dati sul programma sort, si può chiudere il lato "to" della pipe, e quindi iniziare a leggere i dati ordinati via getline. Per esempio:

BEGIN {
    comando = "LC_ALL=C sort"
    n = split("abcdefghijklmnopqrstuvwxyz", a, "")

    for (i = n; i > 0; i--)
        print a[i] |& comando
    close(comando, "to")

    while ((comando |& getline line) > 0)
        print "ricevuto", line
    close(comando)
}

Questo programma scrive le lettere dell’alfabeto in ordine inverso, uno per riga, attraverso la pipe bidirezionale verso sort. Poi chiude la direzione di scrittura della pipe, in modo che sort riceva un’indicazione di fine-file. Questo fa in modo che sort ordini i dati e scriva i dati ordinati nel programma gawk. Una volta che tutti i dati sono stati letti, gawk termina il coprocesso ed esce.

Come nota a margine, l’assegnamento ‘LC_ALL=C’ nel comando sort assicura che sort usi l’ordinamento tradizionale di Unix (ASCII). Ciò non è strettamente necessario in questo caso, ma è bene sapere come farlo.

Occorre prestare attenzione quando si chiude il lato "from" di una pipe bidirezionale; in tal caso gawk attende che il processo-figlio termini, il che può causare lo stallo del programma awk in esecuzione. (Per questo motivo, questa particolare funzionalità è molto meno usata, in pratica, di quella che consente la possibilità di chiudere il lato "to" della pipe.)

ATTENZIONE: Normalmente, è un errore fatale (che fa terminare il programma awk) scrivere verso il lato "to" di una pipe bidirezionale che è stata chiusa, e lo stesso vale se si legge dal lato "from" di una pipe bidirezionale che sia stata chiusa.

È possibile impostare PROCINFO["comando", "NONFATAL"] per far sì che tali operazioni non provochino la fine del programma awk. Se lo si fa, è necessario controllare il valore di ERRNO dopo ogni istruzione print, printf, o getline. Vedi la sezione Abilitare continuazione dopo errori in output per ulteriori informazioni.

Per le comunicazioni bidirezionali si possono anche usare delle pseudo tty (pty) al posto delle pipe, se il sistema in uso le prevede. Questo vien fatto, a seconda del comando da usare, impostando un elemento speciale nel vettore PROCINFO (vedi la sezione Variabili predefinite con cui awk fornisce informazioni), in questo modo:

comando = "sort -nr"           # comando, salvato in una variabile
PROCINFO[comando, "pty"] = 1   # aggiorna PROCINFO
print … |& comando       # avvia la pipe bidirezionale
…

Se il sistema in uso non ha le pty, o se tutte le pty del sistema sono in uso, gawk automaticamente torna a usare le pipe regolari.

Usare le pty in genere evita i problemi di stallo del buffer descritti precedentemente, in cambio di un piccolo calo di prestazioni. Ciò dipende dal fatto che la gestione delle tty è fatta una riga per volta. Su sistemi che hanno il comando stdbuf (parte del pacchetto GNU Coreutils), si può usare tale programma, invece delle pty.

Si noti anche che le pty non sono completamente trasparenti. Alcuni codici di controllo binari, come Ctrl-d per indicare la condizione di file-file, sono interpetati dal gestore di tty e non sono passati all’applicazione.

ATTENZIONE: In ultima analisi, i coprocessi danno adito alla possibilità di uno stallo (deadlock) tra gawk e il programma in esecuzione nel coprocesso. Ciò può succedere se si inviano “troppi” dati al coprocesso, prima di leggere dati inviati dallo stesso; entrambi i processi sono bloccati sulla scrittura dei dati, e nessuno dei due è disponibile a leggere quelli che sono già stati scritti dall’altro. Non c’è modo di evitare completamente una tale situazione; occorre una programmazione attenta, insieme alla conoscenza del comportamento del coprocesso.

L’esempio seguente, preparato da Andrew Schorr, dimostra come l’utilizzo delle pty può servire a evitare situazioni di stallo connesse con i buffer

Si supponga che gawk non sia in grado di sommare dei numeri. Si potrebbe usare un coprocesso per farlo. Ecco un programma fin troppo semplice, che può svolgere tale funzione:

$ cat add.c
#include <stdio.h>

int
main(void)
{
    int x, y;
    while (scanf("%d %d", & x, & y) == 2)
        printf("%d\n", x + y);
    return 0;
}
$ cc -O add.c -o add	Compilazione del programma

Si potrebbe poi scrivere un programma gawk fin troppo semplice, per sommare dei numeri passandoli al coprocesso:

$ echo 1 2 |
> gawk -v cmd=./add '{ print |& cmd; cmd |& getline x; print x }'

E il programma andrebbe in stallo, poiché, add.c non chiama a sua volta ‘setlinebuf(stdout)’. Il programma add si blocca.

Ora, si provi invece con:

$ echo 1 2 |
> gawk -v cmd=add 'BEGIN { PROCINFO[cmd, "pty"] = 1 }
>                  { print |& cmd; cmd |& getline x; print x }'
-| 3

Usando una pty, gawk fa sì che la libreria di I/O determini di avere a che fare con una sessione interattiva, e quindi utilizzi per default una riga alla volta come buffer. E ora, magicamente, funziona!


Note a piè di pagina

(91)

Michael Brennan suggerisce l’uso di rand() per generare nomi-file unici. Questo è un punto valido; tuttavia, i file temporanei rimangono più difficili da usare delle pipe bidirezionali.

(92)

Questo è molto diverso dallo stesso operatore nella C shell e in Bash.


Successivo: , Precedente: , Su: Funzionalità avanzate   [Contenuti][Indice]