Flavio Poletti
Redirezione
http://www.perl.it/documenti/articoli/2008/06/redirezione.html
© 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 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 che le considerazioni per stdout possono essere applicate (quasi) per intero.

I Flussi Standard In Perl

I flussi standard in Perl sono rappresentati da tre filehandle dai nomi poco sorprendenti (ma in lettere maiuscole, attenzione!): STDIN, STDOUT e STDERR. (Ok, in realtà probabilmente potrete utilizzare anche le lettere minuscole, ma non lo fate.)

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 si va per default su STDOUT:

   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:

  • un descrittore è un indice condiviso fra processo e sistema operativo per effettuare operazioni di I/O (in particolare, è un indice che punta ad un elemento di una tabella dei file che il sistema operativo tiene per il dato processo);

  • un filehandle è uno ``strumento'' a disposizione del programmatore per istruire il processo sulle operazioni di I/O da effettuare, che include anche possibili operazioni avanzate come buffering, encoding ecc.

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 fileno():

   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 $output dovrebbe essere usato tutte le volte che volete stampare un'uscita del programma.

Come?!?!? Devo cambiare tutte le uscite?!?

Beh, con questa tecnica sì, magari era meglio pensarci prima. Altrimenti, potreste redirigere STDOUT sul file e non cambiare una virgola. Un po' meno pulito, forse, ma efficace.

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 stdin e manda le uscite sul proprio stdout. Ci sono mille modi per chiamare un programma esterno in Perl, ma se siete per qualche motivo costretti ad usare ``il vecchio sistema'' di pipe()/fork() 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 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'): $!";

Questa tecnica può risultare particolarmente comoda quando avete un pezzo di codice preesistente, o anche un modulo, che stampano le proprie uscite su STDOUT, ma voi volete catturare queste uscite per potervele ``lavorare'' prima di stamparle effettivamente per farle vedere agli utenti del programma (ad esempio, se volete aggiungere dei numeri di riga, oppure eliminare tutte le parolacce dal testo). Attenzione che, in questo caso, dovete anche tenere una ciambella di salvataggio verso la vera uscita del programma, altrimenti dopo aver effettuato la redirezione non saprete più come comunicare con l'utente. Come farlo... sarà evidente nel seguito (i più curiosi possono saltare subito alla fine dell'articolo, proprio prima delle conclusioni).

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! 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()!

Su Filehandle: Come Fare Veramente

Ci 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 open()? È il momento di mettere alla prova quell'affermazione.

La funzione open() ha circa 2000 varianti, ed una di queste è utilizzata per chiamare la sottostante funzione di sistema dup(), che clona gli elementi nella tabella dei file gestita dal sistema operativo:

   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.

Moduli Poco Rispettosi

Che 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 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

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

Pensandoci a posteriori, è abbastanza chiaro (o almeno a me è risultato chiaro solo pensandoci a posteriori). dup() effettua una copia, a livello di tabella dei file del processo (gestita dal sistema operativo), da un certo descrittore a un altro descrittore. In questo caso, $copia è una variabile inizialmente undef, per cui - dietro le quinte - per prima cosa $copia viene creata come filehandle, cui è associato un descrittore non utilizzato (3 nell'esempio), poi viene effettuata la copia dal descrittore 0 a quello di $copia.

Non tutto il male viene per nuocere: a questo punto $copia può diventare la nostra ``memoria storica'' dell'input originale del processo, e può giocare (come vedremo) un ruolo fondamentale se dovessimo averne di nuovo bisogno. Ma non anticipiamo troppo, e torniamo al nostro problema originale: non perdere il nostro aggancio al descrittore standard.

Una Soluzione

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 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 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;
      # ... 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 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?

Capitalizziamo Quanto Imparato

Il sistema più generale è quello di dup()licare l'elemento che ci interessa nella tabella dei file del processo, per poterlo ripristinare in seguito:

   # 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 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(): $!";

      # ... 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 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... suggerimenti?