Introduzione
Il php è nato nel 1994, in forma molto embrionale rispetto a come lo conosciamo oggi, ma fondamentalmente come linguaggio di scripting per il web. Da allora sono state rilasciate diverse versioni e da alcune ormai il php può essere utillizzato anche per la realizzazione di script e programmi di tipo standalone (cioè funzionanti senza l'ausilio di un server web) sia a linea di comando (prompt dos o shell) sia a interfaccia grafica (php-gtk). L'obiettivo di questo articolo è quello di analizzare proprio la scrittura di script funzionanti da linea di comando.
Utilità
La principale utilità di questo tipo di programmazione è ovviamente quella di utilizzare un linguaggio semplice e ricco di funzionalità come il php (basti pensare alle varie librerie per immagini, Pdf, connessione ai database) per creare programmi in tempi relativamente brevi.
Chi dispone di un server linux ad esempio conosce di sicuro l'utilità di tutta una serie di script ad hoc che automatizzino quei compiti ripetitivi necessari al buon funzionamento di un server.
Pensiamo ad esempio ad uno script che comprima determinate directory scelte da noi e le spedisca per posta elettronica come backup. Uno script del genere può essere scritto anche con il codice specifico della shell. Ma perchè non scriverlo in un linguaggio che già ci è familiare (e che per di più è multipiattaforma?).
Funzionamento di base
Esistono due versioni diverse di php capaci di eseguire script da linea di comando: CGI e CLI.
La versione CGI non è stata creata specificatamente per l'esecuzione di codice indipendente da server e browser quindi di default genererà errori con tag html, avrà un tempo di esecuzione limitato seguendo esattamente le direttive impostate nel php.ini (che se non specificato diversamente potrebbe anche essere quello utilizzato direttamente per il web).
La versione CLI diversamente è stata pensata per la shell, quindi ha attivate di default delle opzioni più "funzionali" a questo diverso ambito come ad esempio la soppressione di header in output.
La versione trattata in questo articolo è la CLI, uscita in forma sperimentale con php 4.2.0 e in versione stabile con php 4.3.0. Nella distribuzione binaria di windows questo eseguibile si dovrebbe trovare nella cartella di installazione di php con il nome di php-cli.exe per quanto riguarda le versioni 4.2.x e in una cartella /cli in quella di installazione con il nome di php.exe per le versioni 4.3.x.
Nella versione linux compilata da sorgenti (salvo cambio di directory da parte dell'utente in fase di compilazione) l'eseguibile si dovrebbe trovare come /usr/bin/php ed essere richiamabile semplicemente come "php" da console.
Per determinare la versione di cui disponete dovete richiamare questo eseguibile passando il parametro "-v".
Il codice proposto resta comunque funzionante anche per la versione CGI, anche se consiglio caldamente il passaggio a versione CLI dove possibile.
Funzionalità
Ovviamente non ci sono le tradizionali variabili $_* ($_GET, $_POST, $_COOKIE), sono però accessibili:
- $_ENV[] (contenente variabili d'ambiente)
- $_SERVER[] (contenente le stesse variabili d'ambiente di $_ENV più alcune specifiche)
- $argv[] (contenente i parametri passati all'eseguibile php)
- $argc (intero corrispondente alla conta degli elementi dell'array $argv)
Possiamo fare alcune osservazioni su queste due ultime variabili: mentre nella versione CLI sono sempre accessibili in questo modo, per le versioni CGI seguono il parametro register_globals del php.ini quindi sono accessibili in maniera diretta se questo parametro è impostato a 'On' e in maniera indiretta attraverso l'array $_SERVER ($_SERVER['argc'], $_SERVER['argv']) in caso contrario. È necessario osservare che $argc corrisponde al numero di elementi dell'array $argv nel momento in cui lo script viene inizializzato per cui se aggiungerete o toglierete elementi dall'array $argv vi dovrete ricordare di sincronizzare la variabile $argc (ad esempio in questo modo: $argc = count($argv);). Inoltre ricordiamoci che il primo elemento dell'array $argv ($argv[0]) è occupato dal percorso completo dello script eseguito.
La versione CLI dispone inoltre di 3 stream, aperti e chiusi automaticamente dall'interprete nel momento di inizializzazione e uscita dallo script, corrispondenti agli stream di input, output e standard error: STDIN, STDOUT, STDERR. È possibile ottenere la compatibilità nelle versioni CGI tramite le funzioni define e fopen.
define(STDIN, fopen('php://stdin', 'r'));
define(STDOUT, fopen('php://stdout', 'w'));
define(STDERR, fopen('php://stderr', 'w'));
Ovviamente ci saranno molto utili le varie funzioni riguardanti il filesystem e specialmente realpath(), getcwd(), is_file(), file_exists() e ovviamente le funzioni per l'esecuzione di programmi esterni come exec(), escapeshellarg().
Impostazione del programma
Innanzitutto dobbiamo considerare se il nostro programma necessiterà di parametri e se questi saranno opzionali oppure se sarà chiamato semplicemente per eseguire vari compiti prefissati senza possibilità di modifica al volo con flag particolari. Cioè dobbiamo decidere se il nostro script sarà del tipo "EseguiLeAzioniPrefissate" oppure del tipo "EseguiLeAzioni -azione1 -azione2 -azione3". Inutile dire che il primo stile di programmi è nettamente più semplice da scrivere anche se molto meno flessibile. Oppure potremmo voler fare un programma funzionante in maniera interattiva con menu e scelte.
function menu(){
echo "Programma per il copia incollann";
echo "Scegli l'opzione che preferisci:n";
echo "a) Copian";
echo "b) Incollan";
echo "c) Escinn";
$lettera = fread(STDIN, 1);
return $lettera;
}
$opzione = menu();
if( $opzione == 'a' ){
Copia();
}
elseif( $opzione == 'b' ){
Incolla();
}
elseif( $opzione == 'c' ){
exit();
}
else{
echo "Errore!n";
exit();
}
Gestire gli errori
È una buona idea impostare una funzione di help ed una per la stampa a video degli errori.
Possiamo decidere se utilizzarla anche per il debug con una serie di livelli di errore visualizzabili impostabile dinamicamente oppure se predisporre soltanto errori fatali che facciano terminare il programma.
function help(){
echo "Utilizzo del programma: NomeDelProgramma [eventuali parametri]", "n";
die();
}
function errore($messaggio, $livello = 2){
// $livello -> 1: Errore fatale, 2: Errore, 3: Debug
if( $GLOBALS['LivelloErrore'] <= $livello or $livello == 1 ){
echo $messaggio, "n";
}
if( $livello == 1 ){
help();
}
}
Potremmo decidere anche di non generare messaggi di errore a video ma di salvarli in un file di testo: ci basterà modificare la funzione errore().
Gestire le incompatibilità
Come abbiamo visto in precedenza ci sono delle incompatibilità per quanto riguarda le versioni CGI e CLI, sarebbe una buona cosa creare una funzione ad hoc che rimedi a queste incompatibilità :
function compatibile(){
$sapi = php_sapi_name(); // Server API (cgi, apache, cli, o altro)
$version = phpversion();
if( $sapi != 'cli' or version_compare($version, '4.3.0', '<') ){
set_time_limit(0);
if( !defined('STDIN') ) define(STDIN, fopen('php://stdin', 'r'));
if( !defined('STDOUT') ) define(STDOUT, fopen('php://stdout', 'w'));
if( !defined('STDERR') ) define(STDERR, fopen('php://stderr', 'w'));
register_shutdown_function( create_function('',
'fclose(STDIN); fclose(STDOUT); fclose(STDERR); return true;') );
$GLOBALS['argc'] = $_SERVER['argc'];
$GLOBALS['argv'] = $_SERVER['argv'];
}
}
Gestire i parametri esterni
Molti programmi (specialmente quelli in stile unix) sono progettati per funzionare senza interattività ma semplicemente con dei parametri passati al momento della chiamata.
Esempio: "ProgrammaConta -finoa 10"
Possiamo usare due funzioni che ci aiutano a trovare elementi in un array: in_array() e array_search(). La differenza tra le due è rappresentata dal fatto che la prima restituisce un valore booleano positivo in caso di riscontro nell'array e negativo nel resto dei casi, mentre array_search() restituisce l'indice dell'array. Nel nostro esempio troviamo utile la seconda funzione dato che il parametro passato è formato da più di un elemento ('-finoa' e '10') e non solamente da un "flag di attivazione".
$FinoA = 5; // Valore predefinito
if( in_array('-finoa', $argv) ){
$pos = array_search('-finoa', $argv);
$FinoA = $argv[$pos+1];
}
Prima di far eseguire al programma istruzioni inutilmente dovreste accertarvi che i parametri siano nel numero giusto oltre che del tipo aspettato. Ad esempio se avete bisogno di un parametro più il nome di un file di testo su cui agire, prima dovete controllare che $argc sia almeno uguale a 3, in secondo luogo che sia presente il parametro obbligatorio. Dopo di che potete controllare l'esistenza del file con file_exists() e accertarvi che si tratti proprio di un file con is_file().
L'esecuzione del programma
Normalmente per eseguire il programma appena scritto dobbiamo richiamare l'eseguibile del php passando determinati parametri.
php.exe -f script.php -- -parametro1 -parametro2
Il parametro '-f' serve per specificare lo script php da eseguire, '--' è invece utile per poter passare allo script parametri funzionanti anche per l'eseguile php. Ad esempio se voleste passare al vostro script un parametro '-h' dovreste utilizzare questo piccolo trucco per evitare che il parametro venga utilizzato dall'eseguibile php. Se utilizzate la versione CGI dell'eseguibile può tornare utile il parametro -q per eliminare gli header in output.
Esistono inoltre soluzioni alternative per l'esecuzione automatica degli script:
Sotto linux è possibile impostare come prima linea del file il path dell'eseguibile php (ad esempio /usr/bin/php)
#!/path/to/php
Fatto questo basta assegnare la proprietà di esecuzione attraverso un chmod 755 allo script.
A questo punto potete richiamare direttamente lo script senza il bisogno di specificare da linea di comando l'interprete php.
Sotto Windows invece è possibile legare i file con estensione .php all'interprete facendo così in modo che con un doppio click venga attivato lo script. Sempre sotto windows è possibile creare un semplice file batch (file con estensione .bat) contenente la linea
c:pathtophpphp.exe script.php
Conclusioni
L'argomento era prevedibilmente troppo vasto per essere esaurito con un articolo. Spero però di avere stuzzicato la fantasia vista l'effettiva utilità di questo strumento.
Per terminare segnalo la pagina del manuale ufficiale dedicata all'argomento.