perlfaq8 - Interazione con il Sistema ($Revision: 1.20 $, $Date: 2006-05-22 21:08:57 $)
Questa sezione delle Perl FAQ copre le domande riguardanti l'interazione
con il sistema. Gli argomenti includono la comunicazione tra i
processi (IPC), il controllo dell'interfaccia utente (tastiera,
schermo e dispositivi di puntamento), e più o meno qualsiasi
altra cosa non riguardante la manipolazione di dati.
Leggete le FAQ e la documentazione specifica riguardante la versione
di perl specifica per il vostro sistema operativo (ad es. perlvms,
perlplan9, ...). Tali risorse dovrebbero contenere informazioni
più dettagliate sulle stravaganze del vostro perl.
La variabile $^O ($OSNAME se usate il modulo English) contiene un'indicazione
del nome del sistema operativo (non il numero della versione) per il quale
il vostro binario perl è stato compilato.
Perché questo è quello che fa: sostituisce il vostro
corrente programma in esecuzione con uno differente. Se invece volete
mantenerlo in esecuzione (com'è probabilmente il caso, se state
facendo questa domanda) utilizzate system().
La maniera con cui si accedono e si controllano le tastiere, schermi e
dispositivi di puntamento (mouse) è dipendente dal sistema.
Provate con i seguenti moduli:
- Tastiera
-
Term::Cap Distribuzione perl standard
Term::ReadKey CPAN
Term::ReadLine::Gnu CPAN
Term::ReadLine::Perl CPAN
Term::Screen CPAN
- Schermo
-
Term::Cap Distribuzione perl standard
Curses CPAN
Term::ANSIColor CPAN
- Mouse
-
Tk CPAN
Alcuni di questi casi specifici sono mostrati quali esempi in altre risposte
in questa sezione della perlfaq.
In generale non potete perché non si sa se il destinatario abbia un
device video a colori. Se siete a conoscenza della disponibilità di
un terminale ANSI che riconosce i colori, potete usare il modulo
Term::ANSIColor da CPAN:
use Term::ANSIColor;
print color("red"), "Stop!\n", color("reset");
print color("green"), "Vai!\n", color("reset");
O come questo:
use Term::ANSIColor qw(:constants);
print RED, "Stop!\n", RESET;
print GREEN, "Vai!\n", RESET;
Controllare l'utilizzo del buffer sull'input è una questione
notevolmente dipendente dal sistema. Su molti sistemi, potete semplicemente
usare il comando stty come mostrato in perlfunc/getc, ma come potete
vedere, questo vi sta già portando in problematiche di
portabilità.
open(TTY, "+</dev/tty") or die "nessun tty: $!";
system "stty cbreak </dev/tty >/dev/tty 2>&1";
$chiave = getc(TTY); # forse questo funziona
# O ANCHE
sysread(TTY, $chiave, 1); # probabilmente questo va
system "stty -cbreak </dev/tty >/dev/tty 2>&1";
Il modulo Term::ReadKey da CPAN offre un'interfaccia semplice da usare che
dovrebbe essere più efficiente che non richiamare stty in una
shell esterna per ogni chiave. Include anche un limitato supporto a Windows.
use Term::ReadKey;
ReadMode('cbreak');
$chiave = ReadKey(0);
ReadMode('normal');
Ad ogni modo, usare il codice richiede l'avere un compilatore C
funzionante e la possibilità di usarlo per configurare e installare un modulo
CPAN. Ecco una soluzione che utilizza un modulo POSIX standard che
è già sui vostri sistemi (assumendo che il vostro
sistema supporti POSIX).
use HotKey;
$chiave = readkey();
Ed ecco il modulo HotKey che nasconde le, talvolta mistificatrici,
chiamate per manipolare le strutture termios di POSIX.
# HotKey.pm
package HotKey;
@ISA = qw(Exporter);
@EXPORT = qw(cbreak cooked readkey);
use strict;
use POSIX qw(:termios_h);
my ($term, $oterm, $echo, $noecho, $fd_stdin);
$fd_stdin = fileno(STDIN);
$term = POSIX::Termios->new();
$term->getattr($fd_stdin);
$oterm = $term->getlflag();
$echo = ECHO | ECHOK | ICANON;
$noecho = $oterm & ~$echo;
sub cbreak {
$term->setlflag($noecho); # ok, non voglio nemmeno echo
$term->setcc(VTIME, 1);
$term->setattr($fd_stdin, TCSANOW);
}
sub cooked {
$term->setlflag($oterm);
$term->setcc(VTIME, 0);
$term->setattr($fd_stdin, TCSANOW);
}
sub readkey {
my $chiave = '';
cbreak();
sysread(STDIN, $chiave, 1);
cooked();
return $chiave;
}
END { cooked() }
1;
Il modo più facile in assoluto per farlo è leggere un tasto in
modalità non bloccante, con il modulo Term::ReadKey da CPAN,
passando come argomento -1 ad indicare il non blocco:
use Term::ReadKey;
ReadMode('cbreak');
if (defined ($carattere = ReadKey(-1)) ) {
# l'input sta aspettando ed era $carattere
} else {
# nessun input sta aspettando
}
ReadMode('normal'); # ripristina le normali impostazioni tty
Se dovete farlo poco frequentemente, usate system:
system("clear");
Se dovete farlo molte volte, salvate la stringa clear di modo che potete
stamparla per 100 volte senza chiamare il programma 100 volte:
$stringa_pulisci = `clear`;
print $stringa_pulisci;
Se state state progettando di fare altre manipolazioni dello schermo,
come la posizione del cursore, ecc, dovreste usare il modulo Term::Cap:
use Term::Cap;
$terminale = Term::Cap->Tgetent( {OSPEED => 9600} );
$stringa_pulisci = $terminale->Tputs('cl');
Se avete il modulo Term::ReadKey installato da CPAN, potete usarlo per
ottenere la larghezza e altezza in caratteri e pixel:
use Term::ReadKey;
($larg_car, $alt_car, $larg_pixel, $alt_pixel) = GetTerminalSize();
Questo è più portabile della ioctl nuda e cruda ma non
così illustrativo:
require 'sys/ioctl.ph';
die "nessun TIOCGWINSZ " unless defined &TIOCGWINSZ;
open(TTY, "+</dev/tty") or die "Nessuna tty: $!";
unless (ioctl(TTY, &TIOCGWINSZ, $winsize='')) {
die sprintf "$0: ioctl TIOCGWINSZ (%08x: $!)\n", &TIOCGWINSZ;
}
($riga, $colonna, $xpixel, $ypixel) = unpack('S4', $grandezzafinestra);
print "(riga,colonna) = ($riga,$colonna)";
print " (xpixel,ypixel) = ($xpixel,$ypixel)" if $xpixel || $ypixel;
print "\n";
(Questa domanda non ha nulla a che fare con il web. Per quello, consultate
un'altra FAQ.)
C'è un esempio in perlfunc/crypt.
Per prima cosa, impostate il terminale in modalità ``no echo'', e
poi leggete normalmente la password. Potete farlo alla vecchia
maniera con la funzione ioctl(), oppure usando i controlli POSIX del
terminale (consultate la pagina del manuale di POSIX o il Camel
Book), oppure con una chiamata al programma stty, con diversi
gradi di portabilità.
In molti sistemi potete anche farlo usando il modulo Term::ReadKey
scaricabile da CPAN, che è più facile da usare e teoricamente
più portabile.
use Term::ReadKey;
ReadMode('noecho');
$password = ReadLine(0);
Ciò dipende dal sistema operativo su cui sta girando il vostro
programma. Nel caso di Unix, le porte seriali saranno accessibili tramite
i file in /dev; su altri sistemi, i nomi dei device saranno senz'altro
diversi. Di seguito sono descritte molte tipologie di problemi comuni a
tutti i tipi di interazione con i device:
- lockfile
-
Il vostro sistema potrebbe utilizzare dei lockfile per controllare gli
accessi multipli. Assicuratevi di seguire il protocollo corretto. Se più
processi leggono da uno stesso device nello stesso momento, il risultato
può essere imprevedibile.
- modo di open
-
Se prevedete di effettuare sia operazioni di lettura che di scrittura
sul device, dovrete aprirlo in modalità di aggiornamento (consultate
perlfunc/``open'' per maggiori dettagli). Potreste volerlo aprire senza
incorrere nel rischio di blocco utilizzando
sysopen() e
O_RDWR|O_NODELAY|O_NOCITY dal modulo Fcntl (incluso nella distribuzione
standard di perl). Consultate perlfunc/``sysopen'' per maggiori
informazioni su questo approccio.
- fine linea
-
Alcuni device si aspetteranno un ``\r'' alla fine di ciascuna linea al
posto di di un ``\n''. In alcuni port di perl, ``\r'' e ``\n'' hanno valori
diversi da quelli ASCII (Unix) usuali di ``\012'' e ``\015''. È possibile
che dobbiate indicare direttamente i valori numerici, utilizzando gli
ottali (``\015''), gli esadecimali (``0x0D''), o specificando un carattere
di controllo (``\cM'').
print DEV "atv1\012"; # sbagliato, per alcuni device
print DEV "atv1\015"; # corretto, per alcuni device
Benché con i file di testo normali un ``\n'' vada benissimo,
non c'è uno schema unificato per la terminazione delle righe che
sia portabile tra Unix, DOS/Win, e Macintosh, a meno di non terminare
TUTTE le linee con ``\015\012'', rimuovendo dall'output ciò di cui
non avete bisogno. Ciò è valido in particolare per l'I/O
da socket e per il flush [completamento delle operazioni di I/O, NdT]
automatico, discusso di seguito.
- flush dell'output
-
Se prevedete che i caratteri raggiungano il vostro device quando li
stampate con print(), vorrete certamente impostare il flush automatico
per tale filehandle. Potete servirvi di
select() e della variabile $|
per controllare il flush automatico (consultate perlvar/$|)
e perlfunc/select, oppure perlfaq5, ``Come faccio a terminare le
operazioni di I/O in corso o a privare del buffer un filehandle di
output? Perché devo fare questo?''):
$vecchioh = select(DEV);
$| = 1;
select($vecchioh);
Troverete anche codice che fa ciò senza una variabile temporanea,
come in
select((select(DEV), $| = 1)[0]);
Oppure, se vi va bene importare qualche migliaio di righe di codice
solo perché avete paura di una piccola variabile $| :
use IO::Handle();
DEV->autoflush();
Come indicato in precedenza, questo non funziona quando si utilizza un
socket di I/O tra Unix e Macintosh. In quel caso dovrete indicare i
terminatori di linea direttamente nel codice.
- input che non blocca
-
Se state eseguendo una
read() o sysread() che blocca, dovrete fare in modo
che un handler di un'allarme indichi un timeout (consultate
perlfunc/alarm). Se avete una open che non blocca, probabilmente avrete
una read che non blocca, il che significa che potreste dover utilizzare
una select() con 4 argomenti per determinare se l'I/O è pronto su quel
device (consultate perlfunc/``select'').
Mentre tentava di leggere dal suo dispositivo per l'identificativo del chiamante, il famoso
Jamie Zawinski <jwz@netscape.com>, dopo aver digrignato un bel po' i denti
e combattuto con sysread, sysopen, tcgetattr di POSIX, e varie altre
funzioni che si perdono nella notte, alla fine ha ottenuto quanto segue:
sub apri_modem {
use IPC::Open2;
my $stty = `/bin/stty -g`;
open2( \*MODEM_IN, \*MODEM_OUT, "cu -l$modem_device -s2400 2>&1");
# l'avvio di cu elimina le impostazioni stty di /dev/tty, anche se
# e` stato aperto su una pipe...
system("/bin/stty $stty");
$_ = <MODEM_IN>;
chomp;
if ( !m/^Connected/ ) {
print STDERR "$0: cu ha stampato `$_' al posto di `Connected'\n";
}
}
Spendendo molti ma molti soldi in hardware dedicato, ma ciò
farà sì che si parli male di voi.
Seriamente, non lo potete fare se sono file delle password di Unix--il
sistema delle password di Unix impiega la cifratura one-way (ad una via).
È più simile all'uso di funzioni di hash che alla cifratura.
La cosa migliore che potete verificare è se qualcos'altro è
codificato dalla funzione di hash nella medesima stringa. Non potete far
tornare un hash alla stringa originale. Programmi come Crack possono
tentare di indovinare le password con la forza (e in maniera intelligente),
ma non garantiscono (non lo possono fare) un rapido successo.
Se siete preoccupati per gli utenti che scelgono cattive password, dovreste
preventivamente controllare quando cambiano la loro password (ad esempio,
modificando passwd(1)).
Diversi moduli possono far partire altri processi che non bloccano il
vostro programma Perl. Potete usare IPC::Open3, Parallel::Jobs, IPC::Run
e alcuni dei moduli POE. Consultate CPAN per maggiori dettagli.
Potete anche usare
system("cmd &")
oppure potete usare fork come documentato in perlfunc/``fork'', con
ulteriori esempi in perlipc. Alcune cose di cui bisogna essere
consapevoli, se si è su un sistema a-la Unix:
- STDIN, STDOUT, e STDERR sono condivisi
-
Sia il processo principale che quello messo in background (il processo
``figlio'') condividono gli stessi filehandle di STDIN, STDOUT e STDERR.
Se entrambi tentano di accedervi contemporaneamente, possono accadere
strane cose. Magari chiudeteli e riapriteli per il figlio. Potreste eludere
questo tramite l'<open> di una pipe (si veda perlfunc/``open'') ma su
alcuni sistemi operativi questo significa che il processo figlio non
può vivere più a lungo del genitore.
- Segnali
-
Dovrete intercettare il segnale SIGCHLD e forse anche SIGPIPE. SIGCHLD
viene inviato quando il processo in background finisce. SIGPIPE viene
inviato quando scrivete su un filehandle il cui processo figlio ha chiuso
(un SIGPIPE non catturato può causare una terminazione silenziosa
del vostro programma). Questo non è un problema con
system("cmd&") .
- Zombie
-
Dovete essere preparati a ``raccogliere'' il processo figlio quando finisce.
$SIG{CHLD} = sub { wait };
$SIG{CHLD} = 'IGNORE';
Potete utilizzare anche un doppio fork. Aspetterete immediatamente con il
wait() il vostro primo figlio, e il demone di init aspetterà con
il wait() vostro nipote una volta che questo è uscito.
unless ($pid = fork) {
unless (fork) {
exec "cosa vuoi fare realmente";
die "exec fallito!";
}
exit 0;
}
waitpid($pid,0);
Consultate perlipc/``Signals'' [``Segnali'', NdT] per altri esempi di codice per fare questo
tipo di cose. Gli zombie non sono un problema con system("prog &") .
Non si può ``intercettare'' un carattere di controllo. Quello che
avviene è che il carattere genera un segnale che viene inviato al
gruppo di processi del terminale in foreground, che potete intercettare nel
vostro processo. I segnali sono documentati nella sezione perlipc/``Signals'' [``Segnali'', NdT]
o nella sezione ``Signals'' del Camel.
Potete impostare i valori dell'hash %SIG in modo che siano le funzioni che
volete che si occupano del segnale. Dopo che il perl ha catturato il segnale,
cerca in %SIG una chiave con lo stesso nome del segnale, poi chiama il valore
della subroutine per quella chiave.
# come una subroutine anonima
$SIG{INT} = sub { syswrite(STDERR, "ahi\n", 5 ) };
# oppure un riferimento ad una funzione
$SIG{INT} = \&ahi;
# oppure il nome della funzione come una stringa
$SIG{INT} = "ahi";
Le versioni di Perl precedenti alla 5.8 avevano nel proprio sorgente C dei gestori
di segnali che intercettavano il segnale ed, eventualmente, eseguivano una
funzione Perl da voi messa in %SIG. Questo víola le regole della gestione dei
segnali a quel livello causando un core dump del perl. A partire dalla versione
5.8.0, il perl cerca in %SIG *dopo* che il segnale è stato catturato,
piuttosto che nel mentre il segnale venga catturato. Le precendeti versioni di
questa domanda erano non corrette.
Se perl è correttamente installato sul vostro sistema e la vostra
libreria shadow propriamente scritta, le funzioni getpwd*() descritte in
perlfunc dovrebbero teoricamente fornire accesso (in sola lettura) alle voci
del file delle shadow password. Per modificare il file, createne uno nuovo
(il formato cambia da sistema a sistema--vedete passwd(5) per le specifiche)
e servitevi di pwd_mkdb(8) per installarlo (consultate pwd_mkdb(8) per
ulteriori dettagli).
Ponendo che il vostro programma sia in esecuzione con sufficienti permessi,
dovreste essere in grado di impostare la data e l'ora del sistema
eseguendo il programma date(1). (Non è possibile impostare l'ora
solamente per un processo). Questo sistema funziona sotto Unix, MS-DOS,
Windows, e NT; l'equivalente VMS è set time.
In ogni caso, se volete semplicemente cambiare il vostro fuso orario,
ve la potete proabilmente cavare impostando una variabile d'ambiente:
$ENV{TZ} = "MST7MDT"; # stile unix
$ENV{'SYS$TIMEZONE_DIFFERENTIAL'}="-5" # vms
system "trn comp.lang.perl.misc";
Se volete una granularità più fine di quella, pari a un
secondo, fornita dalla funzione sleep(), il modo più semplice
è usare la funzione select() come documentato in
perlfunc/``select''. Provate i moduli Time::HiRes e BSD::Itimer
(disponibili su CPAN, e parte della distribuzione standard a partire da
Perl 5.8).
In generale, potreste non essere in grado di farlo. Il modulo Time::Hires
(disponibile su CPAN, e parte della distribuzione standard a partire da
Perl 5.8) fornisce questa funzionalità per alcuni sistemi.
Se il vostro sistema supporta la funzione syscall() in Perl e la chiamata
di sistema gettimeofday(2), allora potreste esser in grado di fare qualcosa
del genere:
require 'sys/syscall.ph';
$TIMEVAL_T = "LL";
$fatto = $inizio = pack($TIMEVAL_T, ());
syscall(&SYS_gettimeofday, $inizio, 0) != -1
or die "gettimeofday: $!";
#################################
# FATE LE VOSTRE OPERAZIONI QUI #
#################################
syscall( &SYS_gettimeofday, $fatto, 0) != -1
or die "gettimeofday: $!";
@inizio = unpack($TIMEVAL_T, $inizio);
@fatto = unpack($TIMEVAL_T, $fatto);
# converte i microsecondi
for ($fatto[1], $inizio[1]) { $_ /= 1_000_000 }
$delta_t = sprintf "%.4f", ($fatto[0] + $fatto[1] )
-
($inizio[0] + $inizio[1] );
La versione 5 del Perl ha aggiunto il blocco END che può essere usato
per simulare atexit(). Ogni blocco END di un package viene chiamato
quando il programma o il thread termina (consultate perlmod per maggiori
dettagli).
Per esempio, potete usare quanto segue per assicurarsi che il vostro
programma che implementa un filtro possa dare un output senza riempire
il disco:
END {
close(STDOUT) || die "il close dello STDOUT e` fallito: $!";
}
Il blocco END non viene chiamato quando un segnale non catturabile termina
il programma, quindi se usate dei blocchi END dovreste anche usare
use sigtrap qw(die normal-signals);
Il meccanismo di gestione delle eccezioni del Perl è il suo
operatore eval(). Potete usare eval() come setjmp e die() come
longjmp. Per dettagli su questi aspetti consultate la sezione sui segnali,
specialmente ``time-out handler for a blocking flock()'' [``gestore di time
out per un flock() non bloccante'', NdT] in perlipc/``Signals'' [``Segnali'', NdT] o la sezione
sui ``Signals'' nel Camel Book.
Se la gestione delle eccezione è tutto quello a cui siete
interessati, provate la libreria exceptions.pl (parte della distribuzione
perl standard).
Se volete la sintassi atexit() (come pure quella per rmexit) provate
il modulo AtExit disponibile su CPAN.
Alcuni sistemi basati su Sys-V, degno di nota Solaris 2.x, ridefiniscono
alcune delle costanti standard riguardanti i socket. Visto che queste
erano costanti tra le varie architetture, erano spesso cablate nel codice
perl. La maniera corretta per affrontare la questione è usare ``use Socket''
per ottenere i valori corretti.
Va notato che anche se SunOS e Solaris sono compatibili a livello binario,
questi valori sono differenti. Chissà perché.
Nella maggior parte dei casi, scrivete un modulo esterno per farlo--consultate
la risposta a ``Dove posso imparare qualcosa sul linking del
C con il Perl? [h2xs, xsubpp]''. Ad ogni modo, se la funzione è
una chiamata di sistema e il vostro sistema supporta syscall(), potete
usare la funzione syscall (documentata in perlfunc).
Ricordatevi di controllare i moduli inseriti nella vostra distribuzione, e
anche CPAN--qualcuno potrebbe aver già scritto un modulo per farlo.
Su Windows, provate Win32::API. Sui Mac, provate Mac::Carbon. Se nessun
modulo dispone di un'interfaccia alla funzione C che vi serve, potete
inserire un po' di C nel vostro sorgente Perl con Inline::C.
Storicamente, essi erano generati dal programma h2ph, incluso nella
distribuzione standard di perl. Questo programma converte le direttive
cpp(1) contenute nei file header C in file contenenti definizioni di
subroutine, come &SYS_gettimer, che potete usare come argomenti alle
vostre funzioni. Non funziona perfettamente, ma nella maggior parte dei
casi risolve i problemi. Semplici file come errno.h, syscall.h, e
socket.h erano perfetti, ma quelli difficili come ioctl.h avevano
quasi sempre bisogno di essere modificati a mano. Ecco come si fa ad
installare i file *.ph:
1. diventate superuser
2. cd /usr/include
3. h2ph *.h */*.h
Se il vostro sistema supporta il caricamento dinamico, per ragioni di
portabilità e sicurezza probabilmente dovreste usare h2xs (anch'esso
parte della distribuzione standard di perl). Questo programma converte i
file header C in estensioni Perl. Consultate perlxstut per informazioni
su come partire con h2xs.
Se il vostro sistema non supporta il caricamento dinamico, probabilmente
fareste comunque meglio ad utilizzare h2xs. Consultate perlxstut ed
the ExtUtils::MakeMaker manpage per maggiori informazioni (in breve, utilizzate
make perl al posto di un semplice make per ricompilare perl con
la nuova estensione statica).
Alcuni sistemi operativi hanno dei bug nel kernel tali da rendere gli
script con setuid intrinsecamente insicuri. Il Perl fornisce un numero
di opzioni (descritto in perlsec) per aggirare tali sistemi.
Il modulo IPC::Open2 (parte della distribuzione standard del Perl)
è un approccio facile da usare, che internamente usa pipe(),
fork() e exec() per eseguire il compito. Assicuratevi, però, di
aver letto gli avvertimenti a proposito dello stallo, nella sua
documentazione (consultate il manuale di the IPC::Open2 manpage). Consultate
perlipc/``Bidirection Communication with Another Process'' e
perlipc/``Bidirection Communication with Yourself'' [``Comunicazione
bidirezionale con un altro processo'' e ``Comunicazione bidirezionale con
voi stessi'', NdT].
Potreste anche usare il modulo IPC::Open3 (parte della distribuzione perl
standard) ma siete avvisati che ha un differente ordine degli argomenti
rispetto a IPC::Open2 (si veda the IPC::Open3 manpage).
State confondendo lo scopo di system() con quello dei backticks (``).
system() esegue un comando e restituisce l'informazione relativa allo
stato di uscita (come valore a 16 bit: i 7 bit inferiori rappresentano
il segnale che ha ucciso il processo, se esiste, e gli 8 bit superiori
rappresentano l'effettivo valore di uscita). I backticks (``) lanciano
il comando e restituiscono quello che è stato mandato su STDOUT.
$stato_in_uscita = system("invia-mail");
$stringa_di_output = `ls`;
Ci sono tre modi principali per lanciare comandi esterni:
system $cmd; # usando system()
$output = `$cmd`; # usando i backtick (``)
open (PIPE, "cmd |"); # usando open()
Con system(), sia STDOUT che STDERR andranno a finire nello stesso posto
in cui andranno a finire lo STDOUT e lo STDERR dello script, a meno che
il comando passato a system() non li rediriga. I backtick e open()
leggono soltanto lo STDOUT del vostro comando.
Potete anche usare la funzione open3() da IPC::Open3. Benjamin
Goldberg ha fornito del codice di esempio:
Per catturare lo STDOUT di un programma, ma scartare il suo STDERR:
use IPC::Open3;
use File::Spec;
use Symbol qw(gensym);
open(NULL, ">", File::Spec->devnull);
my $pid = open3(gensym, \*PH, ">&NULL", "cmd");
while( <PH> ) { }
waitpid($pid, 0);
Per catturare lo STDERR di un programma, ma scartare il suo STDOUT:
use IPC::Open3;
use File::Spec;
use Symbol qw(gensym);
open(NULL, ">", File::Spec->devnull);
my $pid = open3(gensym, ">&NULL", \*PH, "cmd");
while( <PH> ) { }
waitpid($pid, 0);
Per catturare lo STDERR di un programma e far sì che il suo STDOUT vada sul nostro STDERR:
use IPC::Open3;
use Symbol qw(gensym);
my $pid = open3(gensym, ">&STDERR", \*PH, "cmd");
while( <PH> ) { }
waitpid($pid, 0);
Per leggere sia lo STDOUT di un comando che il suo STDERR separatamente, potete
redirezionarli su file temporanei, lasciare che il comando venga eseguito poi leggete
i file temporanei:
use IPC::Open3;
use Symbol qw(gensym);
use IO::File;
local *CATTURAOUT = IO::File->new_tmpfile;
local *CATTURAERR = IO::File->new_tmpfile;
my $pid = open3(gensym, ">&CATTURAOUT", ">&CATTURAERR", "cmd");
waitpid($pid, 0);
seek $_, 0, 0 for \*CATTURAOUT, \*CATTURAERR;
while( <CATTURAOUT> ) {}
while( <CATTURAERR> ) {}
Ma non c'è un vero motivo affinché *entrambi* siano dei file
temporanei... quello che segue dovrebbe funzionare allo stesso modo, senza deadlock:
use IPC::Open3;
use Symbol qw(gensym);
use IO::File;
local *CATTURAERR = IO::File->new_tmpfile;
my $pid = open3(gensym, \*CATTURAOUT, ">&CATTURAERR", "cmd");
while( <CATTURAOUT> ) {}
waitpid($pid, 0);
seek CATTURAERR, 0, 0;
while( <CATTURAERR> ) {}
E sarà anche più veloce, visto che possiamo iniziare ad
elaborare immediatamente lo stdout del programma, piuttosto che aspettare
che il programma finisca.
Con ciascuno di questi, potete modificare il descrittore di file prima
della chiamata:
open(STDOUT, ">logfile");
system("ls");
oppure potete usare la redirezione dei descrittori di file della Bourne shell.
$output = `$cmd 2>un_qualche_file`;
open (PIPE, "cmd 2>un_qualche_file |");
Potete anche usare la redirezione dei descrittori di file per rendere STDERR
un duplicato di STDOUT:
$output = `$cmd 2>&1`;
open (PIPE, "cmd 2>&1 |");
Notate che non si può semplicemente aprire STDERR affinché diventi,
nel vostro programma, un duplicato di STDOUT, evitando di fare ricorso alla
shell per effetturare la redirezione. Questo non funziona:
open(STDERR, ">&STDOUT");
$output_completo = `cmd args`; # stderr continua a sfuggire
Questo fallisce perché open() fa in modo che STDERR vada a finire dove
andava a finire STDOUT al momento della chiamata a open(). I backtick poi
fanno in modo che solo STDOUT vada a finire in una stringa, ma non
modificano STDERR (che continua ad andare laddove andava il vecchio STDOUT).
Notate che per la redirezione, nei backtick, si deve usare la sintassi
della Bourne shell (sh(1)), non quella di csh(1)! Dettagli sul perché
la funzione system(), i backtick e le aperture di pipe usino tutte la
Bourne shell stanno nell'articolo versus/csh.whynot della collezione
``Far More Than You Ever Wanted To Know'' [``Molto più di quello che avete
sempre voluto sapere sul Perl'', NdT] reperibile all'url
http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz . Per catturare allo
stesso tempo lo STDOUT e lo STDERR di un comando:
$output = `cmd 2>&1`; # con i backtick
$pid = open(PH, "cmd 2>&1 |"); # o con una pipe
while (<PH>) { } # con relativa lettura
Per catturare lo STDOUT di un comando, buttando via il suo STDERR:
$output = `cmd 2>/dev/null`; # con i backtick
$pid = open(PH, "cmd 2>/dev/null |"); # o con una pipe
while (<PH>) { } # con relativa lettura
Per catturare lo STDERR di un comando, buttando via il suo STDOUT:
$output = `cmd 2>&1 1>/dev/null`; # con i backtick
$pid = open(PH, "cmd 2>&1 1>/dev/null |"); # o con una pipe
while (<PH>) { } # con relativa lettura
Per scambiare lo STDOUT e lo STDERR di un comando, con lo scopo di
catturarne lo STDERR lasciando che il suo STDOUT si sostituisca al nostro STDERR:
$output = `cmd 3>&1 1>&2 2>&3 3>&-`; # con i backtick
$pid = open(PH, "cmd 3>&1 1>&2 2>&3 3>&-|");# o con una pipe
while (<PH>) { } # con relativa lettura
Per leggere lo STDOUT e lo STDERR di un comando separatamente, è
più facile redirigerli su file separatamente,
e poi leggere da questi file quando il programma è terminato:
system("programma args 1>programma.stdout 2>programma.stderr");
L'ordine è importante in tutti questi esempi, poiché la
shell processa le redirezioni di descrittori di file rigorosamente da
sinistra a destra.
system("prog args 1>tmpfile 2>&1");
system("prog args 2>&1 1>tmpfile");
Il primo comando manda sia lo standard out che lo standard error in un file
temporaneo. Il secondo comando manda nel file solo il vecchio standard
output, e il vecchio standard error finisce soltanto nel vecchio
standard out.
Se il secondo argomento ad una open su pipe contiene metacaratteri della
shell, perl esegue una fork(), e poi una exec() di una shell per
decodificare i metacaratteri e poi lanciare il programma desiderato. Se
il programma non può essere eseguito, il messaggio di errore giunge
alla shell, non a Perl. Tutto ciò che il vostro programma può
scoprire è se è stato possibile avviare con successo la
shell stessa. Tuttavia, potete sempre catturare lo STDERR della shell e
controllare eventuali messaggi di errore. Consultate Come posso catturare STDERR da un comando esterno? contenuto sempre in questo
documento, oppure utilizzate il modulo IPC::Open3.
Se l'argomento di open non contiene metacaratteri della shell, Perl
esegue direttamente il comando, senza utilizzare la shell, e può
correttamente riportare se il comando sia partito o meno.
A rigor di termini, niente. A livello stilistico, non è un buon
modo per scrivere codice manutenibile. Il Perl ha diversi operatori per
l'esecuzione di comandi esterni. I backtick sono uno di questi; essi collezionano
l'output dal comando per usarlo nel vostro progamma. La funzione system è
un'altra; essa non fa così.
Scrivere backtick nel vostro programma manda un chiaro segnale a coloro
che leggono il vostro codice che volete raccogliere l'output del comando.
Perché mandare un chiaro segnale che non è vero?
Si consideri questa linea:
`cat /etc/termcap`;
Avete scordato di controllare $? per vedere se il programma è stato anche
eseguito correttamente. Anche se scrivete
print `cat /etc/termcap`;
questo potrebbe e probabilmente dovrebbe essere scritto come
system("cat /etc/termcap") == 0
or die "il programm cat non e` andato a buon fine!";
con il quale si otterrà l'output velocemente (mentre viene generato,
invece che soltanto alla fine) e si controlla anche il valore
restituito.
system() dà la possibilità di decidere se la sostituzione dei
caratteri jolly deve essere effettuata dalla shell, con i backticks
ciò non è possibile.
La cosa è un po' ingannevole. Non potete semplicemente scrivere il
comando così:
@ok = `grep @opzioni '$stringa_di_ricerca' @nomifile`;
Dal Perl 5.8.0, potete usare open() con più argomenti. Come nel caso
delle chiamate in formato lista a system() e ad exec(), non avviene
alcun escape da parte della shell.
open( GREP, "-|", 'grep', @opzioni, $stringa_di_ricerca, @nomifile );
chomp(@ok = );
close GREP;
Potete anche fare così:
my @ok = ();
if (open(GREP, "-|")) {
while () {
chomp;
push(@ok, $_);
}
close GREP;
} else {
exec 'grep', @opzioni, $stringa_di_ricerca, @nomifile;
}
Come con system(), non si ha un escape da parte della shell nemmeno utilizzando
exec() su una lista. Ulteriori esempi si possono trovare in
perlipc/``Safe Pipe Opens'' [``Aperture Sicure di Pipe'', NdT].
Notate che, se utilizzate Microsoft, non è possibile alcuna soluzione
a questo fastidioso problema. Anche se Perl ha la possibilità di
emulare fork(), non combinate comunque, poiché Microsoft non ha una
API del tipo argc/argv.
Alcune librerie stdio impostano un flag eof (fine del file) che ha
bisogno di essere azzerato. Il modulo POSIX definisce clearerr() che
può essere utilizzato. Questo è tecnicamente il modo
corretto per farlo. Ci sono però altre scapaptoie meno affidabili:
-
Tentare di memorizzare il puntatore della posizione nel file e poi spostarsi su
di esso, come ad esempio:
$dove = tell(LOG);
seek(LOG, $dove, 0);
-
Se questo non funziona, provare a spostarsi in una parte diversa del
file e poi di nuovo alla posizione memorizzata.
-
Se questo non funziona, provare a spostarsi in una parte diversa del
file, leggere qualcosa, e poi spostarsi di nuovo alla posizione memorizzata.
-
Se questo non funziona, lasciar perdere la libreria stdio e utilizzare sysread.
Imparate Perl e riscrivetelo. Seriamente, non c'è un convertitore
semplice. Le cose che nella shell risultano difficili da fare sono
facili in Perl, e questa difficoltà è ciò che
rende quasi impossibile la scrittura di un convertitore shell->perl.
Riscrivendo il codice, penserete a ciò che state veramente
cercando di fare, e (si spera) riuscirete ad uscire dal paradigma del
flusso di dati in pipeline della shell che, sebbene sia conveniente in
certi casi, è origine di molte inefficienze.
Provate i moduli Net::FTP, TCP::Client e Net::Telnet (disponibili
su CPAN). Anche http://www.cpan.org/scripts/netstuff/telnet.emul.shar
vi aiuterà nel simulare il protocollo telnet ma Net::Telnet
è davvero più facile da usare..
Se tutto quello che si vuole fare è simulare l'azione di un
telnet ma non si necessita dell'iniziale procedura di handshake, allora
l'approccio standard a due processi sarà sufficiente:
use IO::Socket; # nuovo nel 5.004
$handle = IO::Socket::INET->new('www.perl.com:80')
|| die "non posso connettermi alla porta 80 su www.perl.com: $!";
$handle->autoflush(1);
if (fork()) { # XXX: undef vuol dire fallimento
select($handle);
print while <STDIN>; # tutto dallo stdin al socket
} else {
print while <$handle>; # tutto dal socket allo stdout
}
close $handle;
exit;
Tanto tempo fa c'era una libreria chiamata chat2.pl (parte della
distribuzione standard del Perl), che non è mai stata realmente
finita. Se la trovate da qualche parte, non usatela. Al giorno d'oggi la
cosa migliore è dare un'occhiata al modulo Expect che si trova
su CPAN, il quale richiede altri due moduli da CPAN, IO::Pty e IO::Stty.
Notate prima di tutto che se lo state facendo per ragioni di sicurezza
(per evitare, ad esempio, che le persone possano vedere le password),
allora dovreste riscrivere i vostri programmi in maniera tale da
non passare mai come argomento l'informazione sensibile. Nascondere gli
argomenti non renderà il vostro programma completamente sicuro.
Per alterare effettivamente la visibilità della linea di comando,
potete fare un assegnamento alla variabile $0, come documentato
in perlvar. Però questo non funzionerà su tutti i
sistemi operativi. I programmi di tipo demone come sendmail pongono il
proprio stato lì, come in:
$0 = "demone [si accettano connessioni]";
- Unix
-
In senso stretto, non può essere fatto--lo script viene eseguito
come un processo differente da quello della shell da cui è partito.
I cambiamenti di un processo non si riflettono al processo genitore--solo in
ogni processo figlio creato dopo il cambiamento. C'è una magia
della shell che permette di aggirare questo mediante l'eval() dell'output
dello script nella vostra shell; per i dettagli controllate la FAQ di
comp.unix.questions .
Assumendo che il vostro sistema supporti questo tipo di cose, mandate
semplicemente un segnale appropriato al processo (consultate perlfunc/``kill'').
È comune mandare prima una segnale TERM, aspettare qualche istante e poi
mandare un segnale KILL per terminarlo.
Se per processo demone intendete un processo detached (dissociato dal
proprio tty) allora il metodo descritto qui sotto funziona con la maggior
parte dei sistemi di tipo Unix. Gli utilizzatori di sistemi operativi
diversi dovrebbero consulatare la documentazione del modulo
Vostro_SO::Process per altre soluzioni.
-
Aprite /dev/tty ed applicategli TIOCNOTTY ioctl. Consultate
tty(4)
per i dettagli. Meglio ancora, potete utilizzare semplicemente la
funzione POSIX::setsid(), il che eviterà di dovervi
preoccupare dei gruppi di processi.
-
Cambiate la directory a /
-
Riaprite STDIN, STDOUT e STDERR di modo che non siano più
connessi al vecchio tty
-
Passate in background eseguendo:
fork && exit;
Il modulo Proc::Daemon, disponibile su CPAN, fornisce una funzione che
esegue tutte queste operazioni per voi.
Bella domanda. A volte -t STDIN e -t STDOUT possono aiutare, altre
volte no.
if(-t STDIN && -t STDOUT) {
print "Ed ora? ";
}
Su sistemi POSIX, potete verificare che il vostro gruppo di processi sia lo
stesso del terminale di controllo; esempio:
use POSIX qw/getpgrp tcgetpgrp/;
open(TTY, "/dev/tty") or die $!;
$tpgrp = tcgetpgrp(TTY);
$pgrp = getpgrp();
if ($tpgrp == $pgrp) {
print "interattivo";
} else {
print "non interattivo";
}
Usate la funzione alarm(), verosimilmente assieme ad un gestore di segnali,
come documentato in perlipc/``Signals'' [``Segnali'', NdT] e nella sezione
sui ``Signals'' nel Camel. Potreste usare al suo posto il più
flessibile modulo Sys::AlarmCall, disponibile su CPAN.
La funzione alarm() non è implementata in tutte le versioni di Windows.
Controllate la documentazione relativa alla vostra specifica versione di Perl.
Usate il modulo BSD::Resource da CPAN.
Usate il codice raccoglitore tratto da perlipc/``Signals'' [``Segnali'', NdT]
per chiamare wait() quando viene ricevuto un SIGCHLD oppure usate la
tecnica di double-fork descritta in perlfaq8/``How do I start a process in the background?'' [``Come faccio a far partire un processo in background?'', NdT].
Il modulo DBI fornisce una interfaccia astratta a molti server e tipi
di database, inclusi Oracle, DB2, Sybase, mysql, Postresql, ODBC e
semplici file. Il modulo DBI accede ogni tipo di database attraverso un
driver di database detto DBD. Potete vedere una lista completa dei driver
disponibili su CPAN: http://www.cpan.org/modules/by-module/DBD/ .
Potete leggere di più riguardo a DBI su http://dbi.perl.org .
Altri moduli che forniscono un accesso più specifico: Win32::ODBC,
Alzabo, iodbc ed altri che si trovano su CPAN Search: http://search.cpan.org .
Non si può. Dovete imitare la chiamata system (consultate
perlipc per del codice di esempio) e poi avere un signal handler
[gestore di segnale, NdT] per il segnale INT, che passi il segnale ai
sottoprocessi. Oppure lo potete verificare:
$rc = system($cmd);
if ($rc & 127) { die "segnale mortale" }
Se siete abbastanza fortunati da usare un sistema che supporta la lettura
non bloccante (lo supportano molti sistemi a-la Unix), avete bisogno
soltanto dei flag O_NDELAY oppure O_NONBLOCK dal modulo Fcntl in
combinazione con sysopen():
use Fcntl;
sysopen(FH, "/pippo/unqualchefile", O_WRONLY|O_NDELAY|O_CREAT, 0644)
or die "non posso aprire /pippo/unqualchefile: $!";
(risposta fornita da brian d foy, <bdfoy@cpan.org>
Quando eseguite uno script Perl, qualcos'altro sta eseguendo lo script per voi e
questo qualcos'altro potrebbe dare in output dei messaggi d'errore. Lo script
potrebbe emettere i propri avvertimenti e messaggi d'errore. Per la maggior
parte del tempo non potrete distinguere chi sta dicendo cosa.
Probabilmente non potete mettere a posto la cosa che esegue il per ma
potete cambiare come il perl manda in output i suoi avvertimenti definendo delle
funzioni warning e die su misura.
Considerate questo script che ha un errore che potreste non notare immediatamente.
#!/usr/locl/bin/perl
print "Ciao Mondo\n";
Ho ottenuto un errore quando l'ho eseguito dalla shell (si dà il caso sia bash).
Potrebbe sembrare che il perl abbia dimenticato di avere una funzione print(), ma la mia
shebang non è il percorso al perl, dunque la shell esegue lo script ed ottengo
l'errore.
$ ./test
./test: line 3: print: command not found
[./test: linea3: print: comando non trovato, NdT]
Una correzione alla buona implica un po' di codice, ma questo potrebbe essere
tutto quello che vi occorre per risolvere il problema.
#!/usr/bin/perl -w
BEGIN {
$SIG{__WARN__} = sub{ print STDERR "Perl: ", @_; };
$SIG{__DIE__} = sub{ print STDERR "Perl: ", @_; exit 1};
}
$a = 1 + undef;
$x / 0;
__END__
Il messaggio perl viene fuori con davanti ``Perl''. Il blocco BEGIN
funziona a tempo di compilazione cosicché anche tutti gli errori di compilazione e i
warning ottengano il prefisso ``Perl:''.
Perl: Useless use of division (/) in void context at ./test line 9.
Perl: Name "main::a" used only once: possible typo at ./test line 8.
Perl: Name "main::x" used only once: possible typo at ./test line 9.
Perl: Use of uninitialized value in addition (+) at ./test line 8.
Perl: Use of uninitialized value in division (/) at ./test line 9.
Perl: Illegal division by zero at ./test line 9.
Perl: Illegal division by zero at -e line 3.
[
| | Perl: Utilizzo inutile della divisione (/) in un contesto vuoto in ./test alla linea 9.
| | Perl: Nome ``main::a'' usato solo una volta: possibile errore di battitura in ./test alla linea 8.
| | Perl: Nome ``main::x'' usato solo una volta: possibile errore di battitura in ./test alla linea 9.
| | Perl: Uso di un valore non inizializzato nell'addizione (+) in ./test alla linea 8.
| | Perl: Uso di un valore non inizializzato nella divisione (/) in ./test alla linea 9.
| | Perl: Divisione per zero illegale in ./test alla linea 9.
| | Perl: Divisione per zero illegale in ./test alla linea 3. |
,NdT]
Se non vedete quel ``Perl:'', non viene dal perl.
Potreste anche conoscere proprio tutti gli errori del perl e benché
ci sia qualcuno che potrebbe conoscerli tutti, probabilmente voi no. Ad ogni modo,
dovrebbero essere tutti nella manpage perldiag. Se non vi trovate l'errore,
probabilmente non è un errore del perl.
Cercare ogni messaggio non è la maniera più semplice, dunque lasciate
che il perl lo faccia per voi. Usate la direttiva diagnostics che converte i normali
messaggi del per in lunghe discussioni sull'argomento.
use diagnostics;
Se non ottenete un paragrafo o due due di un'ampia discussione, potrebbe non essere
stato un messaggio del perl.
La maniera più semplice è avere un modulo anch'esso
chiamato CPAN, che lo fa per voi. Questo modulo viene fornito con la
versione del perl 5.004 e successive.
$ perl -MCPAN -e shell
cpan shell -- CPAN exploration and modules installation (v1.59_54)
ReadLine support enabled
cpan> install Unqualche::Modulo
[shell di cpan - esplorazione e installazione di moduli CPAN (v1.59_54).
Il supporto a Readline è abilitato, NdT]
Per installare manualmente il modulo CPAN o comunque qualsiasi modulo di
CPAN ben educato, seguite questi passaggi:
-
Decomprimete i sorgenti in una zona temporanea.
-
perl Makefile.PL
-
make
-
make test
-
make install
Se la vostra versione del perl è compilata senza caricamento
dinamico, allora dovete semplicemente rimpiazzare il passo 3 (make)
con make perl e otterrete una nuova versione binaria di perl con
la vostra estensione linkata in esso.
Consultate the ExtUtils::MakeMaker manpage per maggiori dettagli sulle estensione di
configurazione. Si veda anche la domanda successiva, ``Qual è la
differenza tra require e use?''.
Perl offre parecchi modi diversi per includere in un file il codice contenuto
in un altro file. Ecco le differenze fra i vari costrutti di inclusione:
1) do $file e` come eval `cat $file`, ma il primo
1.1) cerca in @INC e aggiorna %INC.
1.2) tramanda uno scope lessicale *non correlato* sul codice compilato da eval.
2) require $file e` come do $file, ma il primo
2.1) verifica la ridondanza nel caricamento, saltando i file che sono gia` stati inclusi.
2.2) eleva un'eccezione se non riesce a trovare, compilare o eseguire $file.
3) require Modulo e` come require "Modulo.pm", ma il primo
3.1) traduce ogni "::" nel carattere di separazione delle directory definito sul sistema.
3.2) prepara l'analizzatore sintattico a dirimere le ambiguita` della classe Modulo come oggetto indiretto.
4) use Modulo e` come require Modulo, ma il primo
4.1) carica il modulo durante la compilazione del programma, non durante l'esecuzione.
4.2) importa i simboli e le semantiche dal package dentro quello corrente.
In generale, quello che vi servirà più di frequente
sarà use e un modulo Perl appropriato.
Quando configurate dei moduli, usate le opzioni PREFIX e LIB quando generate i Makefile:
perl Makefile.PL PREFIX=/io/miadir/perl LIB=/mydir/perl/lib
poi o impostate la variabile d'ambiente PERL5LIB prima di eseguire script
che usano i moduli/librerie (consultate perlrun) oppure dichiarate
use lib '/miadir/perl/lib';
Che è quasi lo stesso di
BEGIN {
unshift(@INC, '/miadir/perl/lib');
}
eccetto che il modulo lib controlla le directory dipendenti
dall'architettura. Consultate la lib di Perl per maggiori informazioni.
use FindBin;
use lib "$FindBin::Bin";
use i_vostri_moduli;
Ecco i modi consigliati per modificare il vostro path di inclusione:
la variabile d'ambiente PERLLIB
la variabile d'ambiente PERL5LIB
il flag a riga di comando del perl -Idir
la direttiva use lib, come in
use lib "$ENV{HOME}/lamia_perllib";
L'ultima è particolarmente utile perché è a
conoscenza degli aspetti architetturali dipendenti dalla macchina. Il
modulo di direttive lib.pm è stato incluso per la prima volta con
la versione 5.002 del Perl.
È un file con lo stile del perl4 che definisce i valori per le costanti
di sistema relative alle reti. Talvolta viene formato utilizzando h2ph quando
il Perl viene installato, ma altre volte no. I moderni programmi invece
utilizzano il modulo Socket (use Socket; ).
Copyright (c) 1997-2006 Tom Christiansen, Nathan Torkington e altri autori menzionati.
Tutti i diritti riservati.
Questa documentazione è libera; potete ridistribuirla e/o modificarla
secondo gli stessi termini applicati al Perl.
Indipendentemente dalle modalità di distribuzione, tutti gli esempi di
codice in questo file sono rilasciati al pubblico dominio. Potete, e siete
incoraggiati a farlo, utilizzare il presente codice o qualunque forma
derivata da esso nei vostri programmi per divertimento o per profitto.
Un semplice commento nel codice che dia riconoscimento alle FAQ sarebbe
cortese ma non è obbligatorio.
La versione su cui si basa questa traduzione è ottenibile con:
perl -MPOD2::IT -e print_pod perlfaq8
Per maggiori informazioni sul progetto di traduzione in italiano si veda
http://pod2it.sourceforge.net/ .
Traduzione a cura di Michele Beltrame.
Revisione a cura di dree.
Mon Jun 11 22:02:15 2012
|