Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
Capita a volte di voler redirigere l'output di un programma su un file, o da qualche altra parte.
Altre volte, invece, vorremmo che gli input arrivassero da dove diciamo noi, invece che da dove il
programma pensa che stiano arrivando. A volte, infine, abbiamo bisogno che queste redirezioni
siano temporanee, e dopo un po' vogliamo ``tornare indietro''. Vediamo un po' come fare.
I Flussi Standard
In un programma avete a disposizione tre flussi di input/output detti standard (proprio perch ve li
ritrovate sempre):
stdin
da dove il programma prende i suoi ingressi, normalmente associato alla tastiera;
stdout
dove il programma invia le sue uscite, normalmente associato al terminale;
stderr
dove il programma invia messaggi di errore, che segnalano una condizione anomala ma
che non fanno parte delle uscite. Anche questo, normalmente, associato al terminale.
Nel mondo Unix (sinceramente sono un po' all'oscuro del mondo Windows) questi flussi standard
sono associati a dei descrittori di file, che sono degli indici numerici interi che che il sistema
operativo utilizza per individuare i vari canali di I/O di un processo. In particolare, stdin sempre
associato al descrittore 0, stdout al descrittore 1 e stderr al descrittore 2.
Chiunque abbia giocato un po' con qualcosa di somigliante ad una shell Unix sa che quelle
``associazioni'' di default a tastiera e terminale possono essere aggirate con facilit utilizzando
pipe (con il carattere | a separare due chiamate a due programmi differenti, ove lo stdout di
quello a sinistra viene collegato allo stdin di quello a destra) e redirezioni su file (con il caratteri <
per legare stdin ad un file, e > per legare stdout ad un file). Se volete saperne di pi l'ottima
pagina di manuale della shell (sto pensando a bash) sapr soddisfare tutte le vostre curiosit.
Qui per non ci poniamo il problema di come fare queste redirezioni da fuori, ma di come farle da
dentro al nostro programma in Perl. Nel seguito non parleremo quasi pi di stderr, comunque, visto
Perl Mongers Italia. Tutti i diritti riservati.
1 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
che le considerazioni per stdout possono essere applicate (quasi) per intero.
# va su standard output
appena il caso di dire che una delle ``best practice'' predicate da Damian Conway (autore
emerito in ambito Perl) quella di usare un costrutto un po' pi involuto per dire su quale
filehandle vogliamo stampare:
print {*STDOUT} "Ciao, Mondo!\n";
2 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
Definiamo allora una piccola funzione che ci torner utile nel seguito per cercare di capire cosa
succede:
sub stampa_descrittore {
my ($nome, $filehandle) = @_;
print {*STDERR} "$nome ha descrittore ", fileno($filehandle), "\n";
}
stampa_descrittore(STDIN => \*STDIN);
# STDIN ha descrittore 0
stampa_descrittore(STDOUT => \*STDOUT); # STDOUT ha descrittore 1
stampa_descrittore(STDERR => \*STDERR); # STDERR ha descrittore 2
A questo punto $output dovrebbe essere usato tutte le volte che volete stampare un'uscita del
programma.
3 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
allora dovrete fare un po' di redirezioni prima di chiamare il programma esterno, sulla falsariga di
quanto segue:
my ($figlio_r, $figlio_w, $padre_r, $padre_w);
pipe($figlio_r, $padre_w);
pipe($padre_r, $figlio_w);
defined(my $pid = fork()) or die "fork(): $!";
if ($pid) { # padre
close $figlio_r;
close $figlio_w;
# ... dialoga con il figlio utilizzando:
#
$padre_r per leggere dal figlio
#
$padre_w per scrivere al figlio
}
else {
# figlio
close $padre_r;
close $padre_w;
# dobbiamo capire come fare:
# REDIRIGI STDIN su $figlio_r
# REDIRIGI STDOUT su $figlio_w
exec {$programma} $programma, @ARGOMENTI
or die "exec('$programma'): $!";
}
Le Uova Di Colombo
Il sistema pi semplice per redirigere uno dei filehandle standard in Perl (anzi, forse l'unico vero
modo, come avremo modo di vedere pi avanti) ri-aprire il filehandle su qualcosa di differente.
In questo ci viene in aiuto la funzione open(), ovviamente: con cosa pensavate di aprire un
filehandle?
Su File
Il livello pi base di redirezione piuttosto semplice. Se infatti l'obiettivo quello di redirigere su
file, baster ri-aprire il filehandle sul file relativo:
open STDIN, '<', $file_input or die "open('$file_input'): $!";
open STDOUT, '>', $file_input or die "open('$file_input'): $!";
Per redirigere su file non avete bisogno di chiudere il filehandle prima della nuova open(), perch
Perl lo far automaticamente per voi.
Funziona ed semplice, ma non sempre dovremo redirigere su un file, o su un file che dobbiamo
Perl Mongers Italia. Tutti i diritti riservati.
4 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
ancora aprire. Forse il caso di andare avanti.
Su Una Variabile
A partire da Perl 5.8.x possibile aprire un filehandle che punta ad una variabile in memoria invece
che da qualche altra parte. Per fare questa cosa meravigliosa basta passare, al posto del nome del
file, un riferimento alla variabile da aprire:
my $variabile;
open my $fh, '>', \$variabile or die "open(): $!";
Inutile dirlo, la redirezione dei flussi standard funziona anche in questo caso, esattamente come
descritto per un normale file nella sezione precedente. L'unica avvertenza per la seguente: per
redirigere un filehandle di output (STDOUT o STDERR) sar prima necessario chiuderlo:
close STDOUT;
open STDOUT, '>', \$variabile or die "open('$file_input'): $!";
5 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
open my $fh, '>', 'prova.txt' or die "open(): $!";
*STDOUT = $fh;
# Redirezione!
print "Ciao, Mondo!\n";
# stampa su prova.txt
Per casi molto semplici pu andare bene, anche se probabilmente non vi guarderanno molto bene
se fate qualcosa del genere. In particolare, occorre prestare attenzione al fatto che questa
operazione snatura i flussi, dissociandoli dal loro significato di ``flussi standard'' che hanno a livello
di sistema operativo. Che significa? Ci arriviamo subito, basta vedere cosa sta succedendo in
basso, sui descrittori:
open my $fh, '>', 'prova.txt' or die "open(): $!";
stampa_descrittore(STDOUT => \*STDOUT); # STDOUT ha descrittore 1
stampa_descrittore(fh
=> $fh);
# fh ha descrittore 3
*STDOUT = $fh;
# Redirezione!
stampa_descrittore(STDOUT => \*STDOUT); # STDOUT ha descrittore 3
print "Ciao, Mondo!\n";
# stampa su prova.txt
Attenzione! STDOUT ha cambiato descrittore! come se avessimo tolto il mantello con scritto
STDOUT dal descrittore standard numero 1 e l'avessimo avvolto intorno al descrittore numero 3.
Per il codice la chiamata a print() continua ad essere diretta su STDOUT, ma per il sistema
operativo le chiamate di I/O sono rivolte al descrittore 3, che punta al file e non il descrittore
standard per l'output.
Quanto pu importarci di questa cosa? Dipende. Se l'obiettivo era unicamente redirigere le varie
print() nel programma, questo approccio se la cava egregiamente. Ma se vogliamo fare la
redirezione per poi chiamare un altro programma con exec() questo sistema fallir miseramente,
perch in fase di esecuzione del nuovo programma i descrittori considerati per i flussi standard
sono sempre 0, 1 e 2, e questi coincidono con quelli del processo prima della chiamata ad
exec()!
6 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
open my $fh, '>', 'prova.txt' or die "open(): $!";
stampa_descrittore(STDOUT => \*STDOUT); # STDOUT ha descrittore 1
stampa_descrittore(fh
=> $fh);
# fh ha descrittore 3
open STDOUT, '>&', $fh or die "dup/open(): $!"; # Redirezione!
stampa_descrittore(STDOUT => \*STDOUT); # STDOUT ha descrittore 1
print "Ciao, Mondo!\n";
# stampa su prova.txt
Benissimo, quindi! Mettendo il carattere di e commerciale & dopo il carattere che indica la modalit
di apertura possiamo fare una redirezione a prova di exec()! Una redirezione dello STDIN
sarebbe dunque:
open STDIN, '<&', $filehandle_nuovo_input
or die "dup/open(): $!";
Come detto, dup() lavora a livello di tabella dei file nel sistema operativo: in pratica non fa altro
che copiare un elemento di una tabella in un altro elemento. In questo modo, entrambi gli
elementi puntano verso lo stesso flusso - sia esso un file su disco, un socket per una
comunicazione in rete, o qualsiasi altra cosa sia rappresentata da un file in senso esteso - pur
avendo sorti ormai slegate: se chiudo uno dei due, l'altro rimane vivo e vegeto.
Un Primo Tentativo
La prima cosa che mi venuta in mente quando ho avuto questo problema (con
HTTP::Server::Simple) stato fare qualcosa di questo tipo:
# Ad inizio programma, quando i filehandle sono ancora "intatti"
my $STDIN_originale = \*STDIN;
# ...
# Ad un certo punto il modulo far qualcosa del genere:
*STDIN = $nuovo_input;
# ...
# ...
# Nel programma, allora, proviamo ad invertire
my $STDIN_nuovo = \*STDIN;
*STDIN = $STDIN_originale;
# TENTATIVO di ripristino
open STDIN, '<&', $STDIN_nuovo; # redirezione corretta
Perl Mongers Italia. Tutti i diritti riservati.
7 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
Peccato che non funzioni! Il problema che $STDIN_originale non slegata da STDIN, ma
anzi ne segue le sorti molto da vicino, visto che la ``punta'' direttamente. In poche parole, la
redirezione fatta ``alla buona'' modifica anche $STDIN_originale, vanificando il nostro
tentativo di ``conservazione'':
# Ad inizio programma, quando i filehandle sono ancora "intatti"
my $STDIN_originale = \*STDIN;
stampa_descrittore(STDIN_originale => $STDIN_originale); # stampa:
# STDIN_originale ha descrittore 0
# ...
# Ad un certo punto il modulo far qualcosa del genere:
*STDIN = $nuovo_input;
stampa_descrittore(STDIN => \*STDIN); # STDIN ha descrittore 3
# ...
# ...
stampa_descrittore(STDIN_originale => $STDIN_originale); # stampa:
# STDIN_originale ha descrittore 3
Un Altro Tentativo
Dobbiamo trovare un altro modo per conservare il filehandle originale, quindi. Da notare che una
semplice redirezione dup() non andrebbe bene:
open my $copia, '<&', \*STDIN
or die "dup/open(): $!";
stampa_descrittore(copia => $copia); # copia ha descrittore 3
Una Soluzione
Perl Mongers Italia. Tutti i diritti riservati.
8 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
Ci di cui abbiamo bisogno poter clonare il filehandle originale, facendo in modo da riutilizzare
anche il descrittore: quello che - nel sistema operativo - fa fdopen(). Non sorprende, pertanto,
che fra le 2000 varianti di open() ce ne sia anche una che si comporta come fdopen():
open my $clone, '<&=', \*STDIN
# notare il carattere "="
or die "fdopen/open(): $!";
stampa_descrittore(clone => $clone); # clone ha descrittore 0
Non avevamo detto che open() era la panacea a tutti i nostri problemi di redirezione? Per
aggirare un modulo ``alla buona'', quindi, la sequenza pi corretta sarebbe la seguente:
# Ad inizio programma, quando i filehandle sono ancora "intatti"
open my $STDIN_originale, '<&=', \*STDIN
or die "fdopen/open(): $!";
# ...
# Ad un certo punto il modulo far qualcosa del genere:
*STDIN = $nuovo_input;
# ...
# ...
# Nel programma, allora, proviamo
my $STDIN_nuovo = \*STDIN;
#
*STDIN = $STDIN_originale;
#
open STDIN, '<&', $STDIN_nuovo; #
ad invertire
salva quanto impostato dal modulo
ripristina STDIN, descrittore compreso
redirezione corretta
Ma Ne Abbiamo Bisogno?
Ripristinare la corretta associazione filehandle/descrittore pu essere importante per vari motivi. Se
ci interessa unicamente perch vogliamo chiamare exec() ed essere sicuri di impostare i giusti
flussi standard al programma che stiamo per chiamare, per, probabilmente tutto il lavoro di
salva/ripristina/redirigi correttamente che abbiamo descritto un po' troppo. Quello di cui abbiamo
veramente bisogno, in fondo, poter lavorare sui descrittori standard 0, 1 e 2.
A tal proposito, il modulo POSIX mette a disposizione esattamente la funzione che ci serve, ossia
dup2(). Questa funzione fa un mestiere piuttosto semplice: clona le impostazioni associate ad un
descrittore su un altro descrittore, entrambi di nostra scelta. Nell'esempio pipe()/fork(),
quindi, possiamo cavarcela molto semplicemente:
my ($figlio_r, $figlio_w, $padre_r, $padre_w);
pipe($figlio_r, $padre_w);
pipe($padre_r, $figlio_w);
defined(my $pid = fork()) or die "fork(): $!";
if ($pid) { # padre
close $figlio_r;
close $figlio_w;
Perl Mongers Italia. Tutti i diritti riservati.
9 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
# ... dialoga con il figlio utilizzando:
#
$padre_r per leggere dal figlio
#
$padre_w per scrivere al figlio
}
else {
# figlio
close $padre_r;
close $padre_w;
require POSIX;
POSIX::dup2(fileno($figlio_r), 0);
POSIX::dup2(fileno($figlio_w), 1);
Tornare Indietro
Ora abbiamo abbastanza materiale per poter rispondere anche ad un'altra domanda: come torno
indietro dopo aver fatto una redirezione? Come abbiamo detto prima, ad esempio, potremmo aver
rediretto l'output di una parte del codice per catturarlo in una variabile, in modo da avere la
possibilit di operare qualche trasformazione (come cancellare le parolacce, o aggiungere numeri
di riga) prima di stampare effettivamente. Se ho rediretto l'output, per... come faccio a stampare
effettivamente?
$testo_catturato =~ s{scemo}{**beep**}gi;
# per stampare, posso usare $STDOUT_originale...
print {$STDOUT_originale} "Testo 'ripulito':\n";
# ... oppure ripristinare STDOUT
Perl Mongers Italia. Tutti i diritti riservati.
10 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
open STDOUT, '<&', $STDOUT_originale or die "dup/open(): $!";
print $testo_catturato;
Una Scorciatoia
Avete mai usato la funzione local in un programma Perl? No? Fate bene, perch nella stragrande
maggioranza dei casi non ne avete bisogno. Per...
Quando localizzate una variabile (rigorosamente di pacchetto, non potete farlo con una variabile
my), sostanzialmente state creando una copia temporanea che ``dura'' finch non viene chiuso il
blocco in cui appare questa localizzazione. Ad esempio, se volete leggere un file tutto d'un fiato,
potete impostare $/ ad undef giusto il tempo di effettuare la lettura:
my $contenuto_file = do { # apre un blocco
local $/;
# automaticamente impostata a "undef"
open my $fh, '<', $nomefile or die "open('$nomefile'): $!";
<$fh>;
}; # fine del blocco, $/ viene ripristinata al valore precedente
Visto che i filehandle STDIN, STDOUT e STDERR sono agganciati al GLOB omonimo nel pacchetto
main, un sistema molto rapido per rendere temporanea una redirezione consiste proprio nel
localizzare il GLOB stesso:
my $testo_catturato;
{
local *STDOUT;
open STDOUT, '>', \$testo_catturato or die "open(): $!";
# ...
print
print
print
}
$testo_catturato =~ s{scemo}{**beep**}gi;
# Qui STDOUT ritorna quello originale!
print "Testo 'ripulito':\n";
print $testo_catturato;
Viene stampato:
Testo 'ripulito':
una riga, **beep**
altra riga
ultima riga, **beep**
11 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
Da notare che, in questo caso, non abbiamo avuto bisogno di chiudere il filehandle STDOUT.
Perch? Semplice: a valle dell'operazione di localizzazione, il filehandle STDOUT non ``punta'' pi
all'originale - che dovremmo chiudere - ma indefinito, per cui non c' proprio niente da chiudere.
Questo sistema pu essere utilizzato come tecnica di difesa contro i moduli che effettuano la
redirezione alla buona. Ritornando all'esempio sviluppato in precedenza, una possibile soluzione
alternativa la seguente:
# ...
# Ad un certo punto dovremo chiamare il modulo...
my $STDIN_nuovo;
{
local *STDIN; # Inganno! Ora possiamo chiamare il modulo...
# ... che far qualcosa tipo:
*STDIN = $nuovo_input;
# Prima di uscire da questo blocco:
open $STDIN_nuovo, '<&', \*STDIN; # or die... omesso
}
# Nel programma, allora, proviamo ad invertire
open STDIN, '<&', $STDIN_nuovo; # redirezione corretta
Insomma, local non proprio una funzione con cui giocare troppo - in generale non ne avrete
mai bisogno, per le variabili utilizzate my - ma ha ancora una nicchia di utilit.
Concludendo...
... possiamo dire che redirigere i flussi standard di un programma non particolarmente
complicato, anche se dobbiamo fare attenzione a farlo nella maniera giusta per non avere sorprese
e perdere troppo tempo a capire cosa succede.
Come approfondimento, sicuramente consiglierei di dare un'occhiata al manuale Perl su
perlfunc/open. Se poi vi rimasto il dubbio su cosa siano questi famigerati GLOB, probabilmente
un'occhiata a perldata sarebbe d'uopo.
Se proprio siete curiosi di sapere quando non vi rideranno dietro se usate local, potete leggere
l'articolo di Mark Jason Dominus Coping with Scoping (in inglese), anche disponibile in italiano
nell'ottima traduzione di larsen Lo Scopo Dello Scope. Ok, viene detto che non dovete mai usare
local, ma non proprio vero... date un'occhiata all'articolo (sempre di Mark Jason Dominus),
all'indirizzo Seven Useful Uses for local (in inglese).
Non mi vengono invece in mente moduli che potrebbero esservi utili su questo argomento...
Perl Mongers Italia. Tutti i diritti riservati.
12 of 13
Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
suggerimenti?
Perl Mongers Italia. Tutti i diritti riservati.
13 of 13