popen()
La función popen() de PHP permite ejecutar comandos de shell e interactuar con ellos mediante una tubería de un solo sentido para leer o escribir datos.
Introducción
La función popen() ejecuta un comando de shell en un proceso hijo separado y abre una tubería hacia él — un flujo unidireccional del que puedes leer la salida del comando o escribir datos en él. Es la herramienta de PHP para transmitir datos hacia o desde un programa externo línea por línea, en lugar de esperar a que el comando completo finalice.
Esta página cubre lo que devuelve popen(), sus modos r y w, por qué siempre debes combinarlo con pclose(), cómo difiere de exec() y shell_exec(), y las reglas de seguridad que debes seguir antes de pasar cualquier entrada de usuario a un shell.
Cuándo usar popen()
Utiliza popen() cuando necesites un flujo, no un resultado de una sola vez:
- Modo lectura (
"r") — procesa la salida de un comando de forma incremental, por ejemplo, supervisar un registro, leer un listado grande defind, o extraer filas de una CLI de base de datos sin almacenar todo en memoria. - Modo escritura (
"w") — envía datos a la entrada estándar de un comando, por ejemplo, canalizar texto agzip,mailo un filtro personalizado.
Si solo quieres la salida completa de un comando como string, shell_exec() o exec() es más sencillo. Si necesitas leer y escribir en el mismo proceso a la vez, usa proc_open() — las tuberías de popen() son unidireccionales.
Sintaxis
popen(string $command, string $mode): resource|false$command— el comando de shell a ejecutar, exactamente como lo escribirías en una terminal.$mode— la dirección de la tubería:"r"para leer la salida estándar del comando, o"w"para escribir en su entrada estándar. En algunos sistemas puedes agregar"b"(binario) o"t"(texto), por ejemplo"rb".
Valor de retorno: un recurso de puntero de archivo que pasas a funciones como fgets() y fwrite(), o false si la tubería no se pudo abrir. El puntero no es un identificador de archivo normal — debes cerrarlo con pclose(), nunca con fclose().
Cómo funciona
Cuando llamas a popen(), PHP bifurca un proceso hijo que ejecuta $command a través del shell y conecta uno de sus flujos estándar a una tubería:
- En modo
"r", la tubería está conectada a la stdout del comando — lees lo que el comando imprime. - En modo
"w", la tubería está conectada a la stdin del comando — lo que escribes se convierte en la entrada del comando.
Al transmitir en flujo, puedes comenzar a procesar la salida antes de que el comando haya terminado, lo que mantiene el uso de memoria estable incluso para salidas enormes.
Ejemplos
Ejemplo 1: Leer la salida de 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() comprueba si se ha llegado al final del flujo, fgets() lee una línea a la vez, y pclose() cierra la tubería y espera a que el proceso hijo finalice. Siempre verifica el valor de retorno: si popen() falla, devuelve false, y leer desde false genera errores.
En Windows, reemplaza
ls -lcon el comando equivalente, por ejemplodir.
Ejemplo 2: Escribir en la entrada de 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);Aquí fwrite() envía dos líneas a la entrada estándar de grep. grep filtra por la palabra example, por lo que solo se imprime la primera línea. pclose() luego cierra la tubería.
Ejemplo 3: Comprimir datos al vuelo
<?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);
}Esto canaliza datos directamente en gzip sin escribir un archivo intermedio sin comprimir.
popen() vs. exec() y shell_exec()
| Función | Devuelve | ¿Streaming? | Dirección |
|---|---|---|---|
popen() | un recurso de tubería | sí (lectura o escritura) | unidireccional (stdin o stdout) |
exec() | última línea + array de salida | no | solo salida |
shell_exec() | salida completa como string | no | solo salida |
proc_open() | recurso de proceso | sí | bidireccional (stdin y stdout) |
Usa popen() cuando quieras transmitir en flujo; usa los otros cuando solo necesites el resultado final.
Seguridad: nunca pases entrada de usuario sin procesar
popen() ejecuta su argumento a través del shell, por lo que cualquier entrada de usuario sin escapar es un riesgo de inyección de comandos. Siempre escapa los argumentos antes de construir 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);Usa escapeshellarg() para argumentos individuales y escapeshellcmd() para comandos completos. Mejor aún, evita pasar datos de usuario al shell cuando una función nativa de PHP pueda hacer el trabajo.
Errores comunes
- Olvidar
pclose(). Dejar la tubería abierta filtra el recurso y nunca obtienes el estado de salida del proceso hijo.pclose()devuelve el código de salida del comando. - Usar
fclose()en lugar depclose(). Un recurso depopen()es una tubería de proceso, no un archivo simple — ciérralo conpclose(). - Ignorar un retorno
false. Si el comando no puede iniciarse,popen()devuelvefalse; compruébalo antes de leer o escribir. - Mezclar direcciones. Una sola tubería
popen()es de solo lectura o solo escritura. Para ambas, usaproc_open().
Conclusión
popen() abre una tubería unidireccional hacia un comando de shell para que puedas transmitir su salida ("r") o enviarle datos de entrada ("w") sin almacenar todo en memoria. Siempre verifica el valor de retorno, cierra la tubería con pclose(), y escapa cualquier entrada de usuario con escapeshellarg() antes de que llegue al shell. Para conocer los ayudantes de lectura y escritura usados con la tubería, consulta fgets() y fwrite().