Argumentos de la línea de órdenes en C: argc, argv y getopt_long

Cómo leer los argumentos en C, lo que se escribe detrás del nombre del programa: argc y argv, opciones cortas y largas con getopt_long, valores numéricos comprobados, una ayuda con --help y los códigos de salida que esperan los scripts.

argc y argv

Lo que se escribe detrás del nombre del programa llega a main en dos parámetros: argc, cuántos argumentos hay, y argv, un array de cadenas con cada uno. argv[0] es el nombre con el que se ejecutó el programa y el array termina con un puntero nulo:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = [%s]\n", i, argv[i]);
    }
    printf("argv[argc] es %s\n", argv[argc] == nullptr ? "nulo" : "otra cosa");
    return 0;
}
./argv hola "dos palabras" -x
salidaargc = 4
argv[0] = [./argv]
argv[1] = [hola]
argv[2] = [dos palabras]
argv[3] = [-x]
argv[argc] es nulo

Es el intérprete de órdenes, no el programa, quien separa los argumentos por los espacios y quita las comillas: «dos palabras» llega como un solo argumento. argv[0] es tal como se escribió la orden, ./argv, y por eso los mensajes de error suelen usarlo como nombre del programa.

Opciones con getopt_long

Analizar a mano opciones como -n 5, --lineas=5 o -vn5 es tedioso y fácil de hacer mal. getopt (de POSIX) hace las opciones cortas, y getopt_long (de GNU, también en macOS y los BSD) añade las largas con --:

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

static void ayuda(const char *programa)
{
    printf("Uso: %s [opciones] archivo...\n"
           "  -n, --lineas N    cuántas líneas mostrar (por omisión, 10)\n"
           "  -v, --detalle     explicar lo que hace\n"
           "  -h, --help        mostrar esta ayuda\n", programa);
}

int main(int argc, char *argv[])
{
    long lineas = 10;
    bool detalle = false;
    static const struct option largas[] = {
        {"lineas",  required_argument, nullptr, 'n'},
        {"detalle", no_argument,       nullptr, 'v'},
        {"help",    no_argument,       nullptr, 'h'},
        {nullptr, 0, nullptr, 0},
    };

    int op;
    while ((op = getopt_long(argc, argv, "n:vh", largas, nullptr)) != -1) {
        switch (op) {
        case 'n': {
            char *fin;
            lineas = strtol(optarg, &fin, 10);
            if (*fin != '\0' || lineas <= 0) {
                fprintf(stderr, "%s: -n necesita un número positivo, no «%s»\n", argv[0], optarg);
                return 2;
            }
            break;
        }
        case 'v':
            detalle = true;
            break;
        case 'h':
            ayuda(argv[0]);
            return 0;
        default:
            ayuda(argv[0]);
            return 2;
        }
    }

    if (optind == argc) {
        fprintf(stderr, "%s: falta al menos un archivo\n", argv[0]);
        return 2;
    }
    for (int i = optind; i < argc; i++) {
        if (detalle) printf("Mostraría %ld líneas de ", lineas);
        printf("%s\n", argv[i]);
    }
    return 0;
}

Cómo responde a distintas órdenes:

./opciones -v --lineas 5 informe.txt datos.csv
salidaMostraría 5 líneas de informe.txt
Mostraría 5 líneas de datos.csv
./opciones -n diez informe.txt; echo $?
salida con error./opciones: -n necesita un número positivo, no «diez»
2
./opciones --color informe.txt; echo $?
salida con error./opciones: unrecognized option '--color'
Uso: ./opciones [opciones] archivo...
  -n, --lineas N    cuántas líneas mostrar (por omisión, 10)
  -v, --detalle     explicar lo que hace
  -h, --help        mostrar esta ayuda
2
./opciones -v; echo $?
salida con error./opciones: falta al menos un archivo
2

Lo más importante del ejemplo:

  • La cadena "n:vh" declara las opciones cortas; los dos puntos tras la n dicen que lleva un valor, que llega en optarg. El array largas asocia cada opción larga con su letra y termina con una fila de ceros.
  • Al acabar, optind es el índice del primer argumento que no es una opción: lo que queda son los archivos. GNU getopt reordena argv para que las opciones puedan ir antes o después de los archivos, y -- marca el final de las opciones (para un archivo que empiece por guion).
  • El valor de -n se convierte con strtol comprobando que no sobre nada: atoi("diez") devolvería 0 sin avisar.
  • Los códigos de salida siguen la costumbre de Unix: 0 si todo fue bien, y 2 para un uso incorrecto, como hacen grep o ls. Los errores van a stderr, y --help, que no es un error, a stdout con código 0.
  • En la cadena de opcionesSignifica
  • vopción sin valor: -v
  • n:opción con valor obligatorio: -n 5 o -n5
  • c::valor opcional (extensión de GNU), solo pegado: -c5
  • : al principiogetopt no escribe sus propios mensajes de error

Todo el código de esta página se compiló con GCC 15.3 (C23, -Wall -Wextra, sin avisos) y se ejecutó en Linux, en un contenedor limpio sin acceso a la red, antes de publicarla; las salidas están copiadas de esa ejecución.