popen()
La fonction popen() de PHP permet d'exécuter des commandes shell et d'interagir avec elles via un canal de communication unidirectionnel.
Introduction
La fonction popen() exécute une commande shell dans un processus enfant séparé et ouvre un pipe vers celui-ci — un flux unidirectionnel depuis lequel vous pouvez lire la sortie de la commande ou y écrire des données en entrée. C'est l'outil PHP pour diffuser des données vers ou depuis un programme externe ligne par ligne, sans attendre la fin complète de la commande.
Cette page explique ce que popen() retourne, ses modes r et w, pourquoi il faut toujours l'associer à pclose(), en quoi elle diffère de exec() et shell_exec(), et les règles de sécurité à respecter avant de transmettre des données utilisateur à un shell.
Quand utiliser popen()
Utilisez popen() lorsque vous avez besoin d'un flux, et non d'un résultat ponctuel :
- Mode lecture (
"r") — traiter la sortie d'une commande de façon incrémentielle, par exemple surveiller un journal, lire un grand résultat defind, ou récupérer des lignes d'un CLI de base de données sans tout mettre en mémoire. - Mode écriture (
"w") — envoyer des données vers l'entrée standard d'une commande, par exemple transmettre du texte àgzip,mail, ou un filtre personnalisé.
Si vous voulez simplement obtenir la sortie complète d'une commande sous forme de chaîne, shell_exec() ou exec() est plus simple. Si vous devez lire et écrire dans le même processus simultanément, utilisez proc_open() — les pipes de popen() sont unidirectionnels.
Syntaxe
popen(string $command, string $mode): resource|false$command— la commande shell à exécuter, exactement comme vous la saisiriez dans un terminal.$mode— la direction du pipe :"r"pour lire la sortie standard de la commande, ou"w"pour écrire vers son entrée standard. Sur certains systèmes, vous pouvez ajouter"b"(binaire) ou"t"(texte), par exemple"rb".
Valeur de retour : une ressource de type pointeur de fichier à passer aux fonctions comme fgets() et fwrite(), ou false si le pipe n'a pas pu être ouvert. Ce pointeur n'est pas un descripteur de fichier ordinaire — vous devez le fermer avec pclose(), jamais avec fclose().
Fonctionnement
Lorsque vous appelez popen(), PHP crée un processus enfant qui exécute $command via le shell et connecte l'un de ses flux standard à un pipe :
- En mode
"r", le pipe est relié à la stdout de la commande — vous lisez ce qu'elle affiche. - En mode
"w", le pipe est relié à la stdin de la commande — ce que vous écrivez devient l'entrée de la commande.
Comme il s'agit d'un flux, vous pouvez commencer à traiter la sortie avant que la commande ne soit terminée, ce qui maintient une empreinte mémoire faible même pour des sorties volumineuses.
Exemples
Exemple 1 : Lecture de la sortie d'une commande
<?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() vérifie si la fin du flux a été atteinte, fgets() lit une ligne à la fois, et pclose() ferme le pipe et attend la fin du processus enfant. Vérifiez toujours la valeur de retour : si popen() échoue, elle retourne false, et lire depuis false déclenche des erreurs.
Sur Windows, remplacez
ls -lpar la commande équivalente, par exempledir.
Exemple 2 : Écriture vers l'entrée d'une commande
<?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);Ici, fwrite() envoie deux lignes vers l'entrée standard de grep. grep filtre pour le mot example, donc seule la première ligne est affichée. pclose() ferme ensuite le pipe.
Exemple 3 : Compression de données à la volée
<?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);
}Cet exemple envoie des données directement dans gzip sans écrire de fichier intermédiaire non compressé.
popen() vs. exec() et shell_exec()
| Fonction | Retourne | Streaming ? | Direction |
|---|---|---|---|
popen() | une ressource pipe | oui (lecture ou écriture) | unidirectionnel (stdin ou stdout) |
exec() | dernière ligne + tableau de sortie | non | sortie uniquement |
shell_exec() | sortie complète sous forme de chaîne | non | sortie uniquement |
proc_open() | ressource de processus | oui | bidirectionnel (stdin et stdout) |
Utilisez popen() lorsque vous souhaitez streamer ; utilisez les autres lorsque vous avez simplement besoin du résultat final.
Sécurité : ne jamais transmettre des données utilisateur brutes
popen() exécute son argument via le shell, donc toute entrée utilisateur non échappée représente un risque d'injection de commande. Échappez toujours les arguments avant de construire une commande :
<?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);Utilisez escapeshellarg() pour les arguments individuels et escapeshellcmd() pour les commandes entières. Mieux encore, évitez de transmettre des données utilisateur au shell lorsqu'une fonction PHP native peut accomplir la tâche.
Pièges courants
- Oublier
pclose(). Laisser le pipe ouvert fait fuir la ressource et vous ne récupérez jamais le statut de sortie de l'enfant.pclose()retourne le code de sortie de la commande. - Utiliser
fclose()à la place depclose(). Une ressourcepopen()est un pipe de processus, pas un fichier ordinaire — fermez-la avecpclose(). - Ignorer un retour
false. Si la commande ne peut pas démarrer,popen()retournefalse; vérifiez-le avant de lire ou d'écrire. - Mélanger les directions. Un pipe
popen()unique est en lecture seule ou en écriture seule. Pour les deux, utilisezproc_open().
Conclusion
popen() ouvre un pipe unidirectionnel vers une commande shell afin de streamer sa sortie ("r") ou de lui envoyer des données ("w") sans tout mettre en mémoire tampon. Vérifiez toujours la valeur de retour, fermez le pipe avec pclose(), et échappez toute entrée utilisateur avec escapeshellarg() avant qu'elle n'atteigne le shell. Pour en savoir plus sur les fonctions de lecture et d'écriture utilisées avec le pipe, consultez fgets() et fwrite().