| |||
| © Perl Mongers Italia. Tutti i diritti riservati. | |||
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 StandardIn un programma avete a disposizione tre flussi di input/output detti standard (proprio perché ve li ritrovate sempre):
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 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 che le considerazioni per stdout possono essere applicate (quasi) per intero.
I Flussi Standard In PerlI flussi standard in Perl sono rappresentati da tre filehandle dai nomi
poco sorprendenti (ma in lettere maiuscole, attenzione!): I flussi standard in Perl si usano come qualsiasi altro filehandle: my $un_input = <STDIN>; print STDOUT "Ciao, Mondo!\n"; print STDERR "Ooops, hai commesso un errore!\n"; Se non mettete nessun filehandle in una print "Ciao, Mondo!\n"; # 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"; # fa quello che ci si aspetta
ma stiamo veramente divagando. Va osservata una differenza fra filehandle e descrittori:
Ovviamente i due concetti sono fortemente correlati fra loro: un
filehandle è una specie di mantello intorno al descrittore;
alla fin fine quando il processo decide di effettuare l'I/O utilizza
proprio il descrittore associato al filehandle. Per impicciarci su
questa associazione possiamo utilizzare la funzione my $descrittore = fileno $filehandle; 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
Perché Redirigere, Poi?Beh, questo è veramente fuori dall'ambito di questo articolo, non vi pare? Se non vi interessa, che ve lo leggete a fare? Scherzi a parte, potrebbero esserci molti motivi per fare una cosa del genere. Un primo esempio è il seguente: avete un programma già fatto, e volete aggiungere un'opzione di chiamata per cui le uscite vanno su file anziché su terminale. Come fare? Una soluzione è stabilire, all'inizio del programma, quale debba essere il filehandle di uscita, dipendentemente dalle opzioni che vi sono state passate, qualcosa tipo:
my $output;
if (exists $opzioni{output}) {
open $output, '>', $opzioni{output}
or die "open('$opzioni{output}'): $!";
}
else {
$output = \*STDOUT;
}
A questo punto Come?!?!? Devo cambiare tutte le uscite?!? Beh, con questa tecnica sì, magari era meglio pensarci prima. Altrimenti,
potreste redirigere Un altro caso in cui questa redirezione risulta utile è quando volete
chiamare un programma esterno che - senza che ci sorprendiamo troppo -
prende i suoi ingressi dal proprio 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 ColomboIl 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
Su FileIl 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 Funziona ed è semplice, ma non sempre dovremo redirigere su un file, o su un file che dobbiamo ancora aprire. Forse è il caso di andare avanti.
Su Una VariabileA 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'): $!";
Questa tecnica
può risultare particolarmente comoda quando avete un pezzo di codice
preesistente, o anche un modulo, che stampano le proprie uscite su
Su Filehandle: Un Buco Nell'Acqua (O Quasi)In qualche modo - da un modulo bellissimo che avete appena trovato, o per sola imposizione delle mani - vi ritrovate un filehandle che vorreste sostituire ad uno dei flussi standard. Il metodo più semplice che può venire in mente è far ``puntare'' i filehandle dove vogliamo noi. I filehandle standard ``vivono'' nel pacchetto main, in dei GLOB che hanno lo stesso nome (no, non vi tedierò spiegando cos'è un GLOB, tranquilli). In maniera molto cruda, possiamo fare qualcosa del genere: *STDIN = $filehandle_nuovo_input; *STDOUT = $filehandle_nuovo_output; Cominciamo subito a dire che questo approccio funziona: 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! Quanto può importarci di questa cosa? Dipende. Se l'obiettivo era unicamente
redirigere le varie
Su Filehandle: Come Fare VeramenteCi sarà pure un sistema, allora? Ebbene sì, il sistema c'è ed è fare in
modo da non cambiare il descrittore di STDOUT. Lapalissiano? Forse.
Ricordate quando s'è detto che - probabilmente - l'unico modo per effettuare
una redirezione come si deve è usare La funzione 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
open STDIN, '<&', $filehandle_nuovo_input
or die "dup/open(): $!";
Come detto,
Moduli Poco RispettosiChe succede se un modulo che vogliamo utilizzare non si attiene alla forma pulita ed effettua redirezioni alla buona? Non è una domanda oziosa per allungare un po' il brodo di questo articolo, ma anzi il motivo principale per cui mi è venuto in mente di scriverlo!
Un Primo TentativoLa 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 Peccato che non funzioni! Il problema è che # 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 TentativoDobbiamo trovare un altro modo per conservare il filehandle originale,
quindi. Da notare che una semplice redirezione
open my $copia, '<&', \*STDIN
or die "dup/open(): $!";
stampa_descrittore(copia => $copia); # copia ha descrittore 3
Pensandoci a posteriori, è abbastanza chiaro (o almeno a me è risultato
chiaro solo pensandoci a posteriori). Non tutto il male viene per nuocere: a questo punto
Una SoluzioneCiò di cui abbiamo bisogno è poter
clonare il filehandle originale, facendo in modo da riutilizzare anche
il descrittore: è quello che - nel sistema operativo - fa
open my $clone, '<&=', \*STDIN # notare il carattere "="
or die "fdopen/open(): $!";
stampa_descrittore(clone => $clone); # clone ha descrittore 0
Non avevamo detto che
# 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 ad invertire my $STDIN_nuovo = \*STDIN; # salva quanto impostato dal modulo *STDIN = $STDIN_originale; # ripristina STDIN, descrittore compreso open STDIN, '<&', $STDIN_nuovo; # 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 A tal proposito, il modulo POSIX mette a disposizione esattamente la
funzione che ci serve, ossia 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;
require POSIX; # contiene la funzione dup2
POSIX::dup2(fileno($figlio_r), 0); # REDIRIGI stdin su $figlio_r
POSIX::dup2(fileno($figlio_w), 1); # REDIRIGI stdout su $figlio_w
exec {$programma} $programma, @ARGOMENTI
or die "exec('$programma'): $!";
}
Tornare IndietroOra 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?
Capitalizziamo Quanto ImparatoIl sistema più generale è quello di # salva STDOUT per poterlo riutilizzare in seguito open my $STDOUT_originale, '<&', \*STDOUT or die "dup/open(): $!"; my $testo_catturato; close STDOUT; # Ricordate? Va chiuso quando si opera con la variabile! open STDOUT, '>', \$testo_catturato or die "open(): $!"; # ... ora, la "print" opera su $testo_catturato! print "una riga, scemo!\n"; print "altra riga\n"; print "ultima riga, scemo!\n";
$testo_catturato =~ s{scemo}{**beep**}gi;
# per stampare, posso usare $STDOUT_originale...
print {$STDOUT_originale} "Testo 'ripulito':\n";
# ... oppure ripristinare STDOUT open STDOUT, '<&', $STDOUT_originale or die "dup/open(): $!"; print $testo_catturato;
Una ScorciatoiaAvete mai usato la funzione Quando
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
my $testo_catturato;
{
local *STDOUT;
open STDOUT, '>', \$testo_catturato or die "open(): $!";
# ... ora, la "print" opera su $cattura!
print "una riga, scemo!\n";
print "altra riga\n";
print "ultima riga, scemo!\n";
}
$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** Da notare che, in questo caso, non abbiamo avuto bisogno di chiudere
il filehandle 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,
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 Non mi vengono invece in mente moduli che potrebbero esservi utili su questo argomento... suggerimenti? | |||