PHP en CLI: $argv, $argc, STDIN/STDOUT/STDERR y scripts de consola

PHP no es solo un lenguaje de servidor web. Desde la línea de comandos, el intérprete php ejecuta scripts que pueden leer argumentos, procesar datos de STDIN, escribir en STDOUT y STDERR y comunicarse con el sistema operativo. Los scripts CLI son perfectos para tareas automatizadas, importaciones de datos, crons y herramientas de administración.

$argv y $argc: argumentos del script

$argv es un array con los argumentos pasados al script. $argv[0] siempre es el nombre del script. $argc es el número total de argumentos, incluido el nombre del script.

<?php
// Ejecutar: php saluda.php Ana 30
// $argv = ['saluda.php', 'Ana', '30']
// $argc = 3

if ($argc < 3) {
    fwrite(STDERR, "Uso: php saluda.php <nombre> <edad>n");
    exit(1); // código de salida distinto de 0 indica error
}

$nombre = $argv[1];
$edad   = (int) $argv[2];

if ($edad < 0 || $edad > 150) {
    fwrite(STDERR, "Edad no válida: $edadn");
    exit(2);
}

echo "Hola, $nombre. Tienes $edad años.n";
exit(0); // éxito
?>

Leer STDIN con fgets()

STDIN es un flujo de lectura predefinido que conecta el script con la entrada estándar. Permite leer datos que el usuario escribe o que se pasan por pipe desde otro comando.

<?php
// Ejecutar: echo "Hola mundo" | php mayusculas.php
// O interactivo: php mayusculas.php (escribe y pulsa Ctrl+D)

// Leer línea a línea hasta EOF
while (($linea = fgets(STDIN)) !== false) {
    echo strtoupper(trim($linea)) . "n";
}

// Leer todo STDIN de golpe (útil para JSON)
// $json = stream_get_contents(STDIN);
// $datos = json_decode($json, true);

// Pedir confirmación al usuario
echo "¿Continuar? (s/n): ";
$respuesta = trim(fgets(STDIN));
if (strtolower($respuesta) !== 's') {
    echo "Cancelado.n";
    exit(0);
}
echo "Continuando...n";
?>

getopt(): opciones con flags

getopt() procesa argumentos con formato Unix. Los argumentos cortos son letras con : si requieren valor. Los largos son palabras con :: si el valor es opcional.

<?php
// Ejecutar: php exportar.php -f csv --limite=100 --verbose
$opciones = getopt('f:v', ['formato:', 'limite::', 'verbose', 'ayuda']);

// -f necesita valor (-f csv)
// -v es un flag sin valor
// --formato= necesita valor (--formato=json)
// --limite= es opcional (--limite o --limite=50)
// --verbose y --ayuda son flags

if (isset($opciones['ayuda'])) {
    echo "Uso: php exportar.php -f <formato> [--limite=N] [--verbose]n";
    exit(0);
}

$formato = $opciones['f'] ?? $opciones['formato'] ?? 'csv';
$limite  = isset($opciones['limite']) ? (int) ($opciones['limite'] ?: 100) : 100;
$verbose = isset($opciones['verbose']) || isset($opciones['v']);

if ($verbose) {
    echo "Exportando en formato $formato, límite $limite registros.n";
}
?>

stream_isatty(): detectar si la salida va a una terminal

stream_isatty() permite detectar si la salida va a una terminal o está siendo redirigida a un fichero o pipe. Esto es útil para mostrar colores ANSI solo cuando la salida es una terminal interactiva.

<?php
function enTerminal(): bool
{
    return function_exists('stream_isatty') && stream_isatty(STDOUT);
}

function colorear(string $texto, string $color): string
{
    if (!enTerminal()) {
        return $texto; // sin colores si la salida va a fichero o pipe
    }
    $codigos = ['rojo' => '31', 'verde' => '32', 'amarillo' => '33', 'azul' => '34'];
    $codigo  = $codigos[$color] ?? '0';
    return "e[{$codigo}m{$texto}e[0m";
}

function ok(string $msg): void   { echo colorear("? $msg", 'verde') . "n"; }
function error(string $msg): void { fwrite(STDERR, colorear("? $msg", 'rojo') . "n"); }
function info(string $msg): void  { echo colorear("? $msg", 'azul') . "n"; }

info('Iniciando proceso...');
ok('Conexión a base de datos establecida.');
error('No se pudo enviar el email de confirmación.');
?>

Separar STDOUT de STDERR

<?php
// STDOUT: resultado normal del script (puede redirigirse a fichero)
// STDERR: mensajes de error y diagnóstico (normalmente visible en terminal)

// Buena práctica: mensajes de progreso a STDERR, datos a STDOUT
// Así el usuario puede hacer: php generar.php > datos.csv 2> errores.log

$procesados = 0;
$errores    = 0;

foreach ($registros as $registro) {
    try {
        $csv = procesarRegistro($registro);
        fwrite(STDOUT, $csv . "n");   // dato útil a STDOUT
        $procesados++;
    } catch (Exception $e) {
        fwrite(STDERR, "Error en registro #{$registro['id']}: " . $e->getMessage() . "n");
        $errores++;
    }
}

fwrite(STDERR, "Proceso completado: $procesados OK, $errores errores.n");
exit($errores > 0 ? 1 : 0);
?>

La documentación oficial sobre PHP en línea de comandos incluye la lista completa de opciones del intérprete, el uso de shebangs para scripts ejecutables directamente, el servidor HTTP de desarrollo integrado y las diferencias de comportamiento entre modo CLI y modo web.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP