popen()
La funzione popen() di PHP esegue comandi shell e apre una pipe per leggerne l'output o scrivere dati in ingresso al processo.
Introduzione
La funzione popen() esegue un comando shell in un processo figlio separato e apre una pipe verso di esso — un flusso unidirezionale da cui è possibile leggere l'output del comando oppure scrivere dati in ingresso. È lo strumento PHP per trasmettere dati da o verso un programma esterno riga per riga, invece di attendere il completamento dell'intero comando.
Questa pagina illustra cosa restituisce popen(), le sue modalità r e w, perché è sempre necessario abbinarla a pclose(), in che cosa si differenzia da exec() e shell_exec(), e le regole di sicurezza da rispettare prima di passare qualsiasi input dell'utente alla shell.
Quando usare popen()
Ricorrere a popen() quando si ha bisogno di un flusso, non di un risultato istantaneo:
- Modalità lettura (
"r") — elaborare in modo incrementale l'output di un comando, ad esempio seguendo un log in tempo reale, leggendo un grande risultato difind, oppure estraendo righe da una CLI di database senza bufferizzare tutto in memoria. - Modalità scrittura (
"w") — inviare dati all'input standard di un comando, ad esempio inviando testo agzip,mail, o un filtro personalizzato.
Se si vuole semplicemente ottenere l'output completo di un comando come stringa, shell_exec() o exec() sono più semplici. Se è necessario leggere e scrivere nello stesso processo contemporaneamente, usare proc_open() — le pipe di popen() sono unidirezionali.
Sintassi
popen(string $command, string $mode): resource|false$command— il comando shell da eseguire, esattamente come lo si digiterebbe in un terminale.$mode— la direzione della pipe:"r"per leggere l'output standard del comando, oppure"w"per scrivere nel suo input standard. Su alcuni sistemi è possibile aggiungere"b"(binario) o"t"(testo), ad esempio"rb".
Valore restituito: una risorsa puntatore a file da passare a funzioni come fgets() e fwrite(), oppure false se la pipe non può essere aperta. Il puntatore non è un normale handle di file — deve essere chiuso con pclose(), mai con fclose().
Come funziona
Quando si chiama popen(), PHP crea un processo figlio che esegue $command tramite la shell e connette uno dei suoi flussi standard a una pipe:
- In modalità
"r", la pipe è collegata allo stdout del comando — si legge ciò che il comando stampa. - In modalità
"w", la pipe è collegata allo stdin del comando — ciò che si scrive diventa l'input del comando.
Poiché opera in streaming, è possibile iniziare a elaborare l'output prima che il comando sia terminato, mantenendo costante l'utilizzo della memoria anche per output di grandi dimensioni.
Esempi
Esempio 1: Lettura dell'output di un comando
<?php
// Read mode: stream the output of a directory listing line by line.
$handle = popen('ls -l', 'r');
if ($handle === false) {
exit("Could not open the pipe.\n");
}
while (!feof($handle)) {
$line = fgets($handle);
echo $line;
}
pclose($handle);feof() verifica se è stata raggiunta la fine del flusso, fgets() legge una riga alla volta, e pclose() chiude la pipe e attende che il processo figlio termini. Verificare sempre il valore restituito: se popen() fallisce, restituisce false, e leggere da false genera errori.
Su Windows, sostituire
ls -lcon il comando equivalente, ad esempiodir.
Esempio 2: Scrittura nell'input di un comando
<?php
// Write mode: pipe a line of text into grep's standard input.
$handle = popen('grep "example"', 'w');
if ($handle === false) {
exit("Could not open the pipe.\n");
}
fwrite($handle, "This line has the word example.\n");
fwrite($handle, "This line does not match.\n");
pclose($handle);Qui fwrite() invia due righe all'input standard di grep. grep filtra per la parola example, quindi solo la prima riga viene stampata. pclose() chiude poi la pipe.
Esempio 3: Compressione dei dati al volo
<?php
// Stream text straight into gzip and save a compressed file.
$handle = popen('gzip > output.txt.gz', 'w');
if ($handle !== false) {
fwrite($handle, "Some data to compress.\n");
pclose($handle);
}Questo invia i dati direttamente a gzip senza scrivere un file intermedio non compresso.
popen() vs. exec() e shell_exec()
| Funzione | Restituisce | Streaming? | Direzione |
|---|---|---|---|
popen() | una risorsa pipe | sì (lettura o scrittura) | unidirezionale (stdin o stdout) |
exec() | ultima riga + array di output | no | solo output |
shell_exec() | output completo come stringa | no | solo output |
proc_open() | risorsa processo | sì | bidirezionale (stdin e stdout) |
Usare popen() quando si vuole operare in streaming; usare le altre funzioni quando si ha bisogno solo del risultato finale.
Sicurezza: non passare mai input utente non elaborato
popen() esegue il suo argomento attraverso la shell, quindi qualsiasi input utente non precedentemente sanificato costituisce un rischio di command injection. Effettuare sempre l'escape degli argomenti prima di costruire un comando:
<?php
$userInput = $_GET['name'] ?? 'world';
// escapeshellarg() wraps the value in quotes and neutralizes shell metacharacters.
$command = 'echo Hello ' . escapeshellarg($userInput);
$handle = popen($command, 'r');
echo fgets($handle);
pclose($handle);Usare escapeshellarg() per i singoli argomenti e escapeshellcmd() per i comandi interi. Meglio ancora, evitare del tutto di passare dati utente alla shell quando una funzione PHP nativa può svolgere il lavoro.
Errori comuni
- Dimenticare
pclose(). Lasciare la pipe aperta spreca la risorsa e non si ottiene mai lo stato di uscita del processo figlio.pclose()restituisce il codice di uscita del comando. - Usare
fclose()invece dipclose(). Una risorsapopen()è una pipe di processo, non un semplice file — chiuderla conpclose(). - Ignorare un valore restituito
false. Se il comando non può avviarsi,popen()restituiscefalse; verificarlo prima di leggere o scrivere. - Confondere le direzioni. Una singola pipe
popen()è di sola lettura o di sola scrittura. Per entrambe, usareproc_open().
Conclusione
popen() apre una pipe unidirezionale verso un comando shell per poterne trasmettere l'output ("r") oppure inviargli dati in ingresso ("w") senza bufferizzare tutto in memoria. Verificare sempre il valore restituito, chiudere la pipe con pclose() ed eseguire l'escape di qualsiasi input utente con escapeshellarg() prima che raggiunga la shell. Per conoscere gli helper di lettura e scrittura utilizzati con la pipe, vedere fgets() e fwrite().