Files

1374 lines
53 KiB
Markdown

# Capítulo 13 — `getopt_long`: el analizador de opciones, explicado bien
> Capítulo intermedio, pero **escrito para que se entienda de verdad**. Se apoya en variables (`02`),
> operadores (`03`), `if` y bucles (`04`), funciones (`05`) y cadenas (`06`).
>
> Es el capítulo más práctico del libro: aprendés **una sola función** (`getopt_long`) y la domas
> hasta que no te encuentres nunca con una herramienta de consola que no sepas interpretar.
> En el siguiente capítulo (`14-entrada-salida-estandar`) vas a profundizar en las tres tuberías
> (`stdin`/`stdout`/`stderr`), que acá solo necesitás para mostrar los resultados.
---
## Cómo leer este capítulo
Hasta ahora, todos tus programas **preguntan** mientras corren: te muestran un cartel y esperan a que
escribas algo por teclado. Las herramientas serias --`ls`, `grep`, `git`, `gcc`-- no hacen eso: les
pasás las órdenes cuando las ejecutás.
Este capítulo te enseña **una sola función** para hacerlo: `getopt_long()`. No tres funciones, no
diez. Una. Pero la vas a entender tan bien que podrás leer el código de cualquier herramienta y saber
exactamente qué hace cada cosa.
Está dividido en **ocho partes**. Después de cada concepto, una **práctica** --uno o más ejercicios
cortos-- para que lo uses ya mismo. No saltees las prácticas: son la mitad del capítulo.
| Parte | De qué habla | La idea en una frase |
|---|---|---|
| **1** | El problema de parsear a mano | Escribir un parser de opciones desde cero es más trabajo del que parece. |
| **2** | La llamada a `getopt_long` | La firma completa, parámetro por parámetro. |
| **3** | Los tres macros | `no_argument`, `required_argument` y `optional_argument`. |
| **4** | El struct de opciones largas | `struct option`: la tabla que conecta nombres largos con códigos cortos. |
| **5** | Las variables globales | `optarg`, `optind`, `optopt`: lo que `getopt_long` deja atrás para que las uses. |
| **6** | Errores y el caso `'?'` | Qué pasa cuando algo sale mal y cómo manejarlo con estilo. |
| **7** | Posicionales después de `optind` | Cómo recuperar los argumentos "de verdad" una vez terminada la magia. |
| **7.5** | La ayuda de uso | Un formato estándar para guiar al usuario (sin función en C, pero con un helper). |
| **8** | El ejercicio jefe | Juntás todo en una herramienta de consola completa. |
Los **ejercicios** se numeran en orden: `13.1`, `13.2`, `13.3`, y así. Cada uno trae su **escenario de
prueba** (qué entra y qué tiene que salir) para que puedas comprobar tu solución.
> **Regla de oro del libro:** el capítulo explica y muestra **cómo funciona** cada cosa, pero **los
> ejercicios no traen la solución**. El enunciado te da el problema; la respuesta la escribís vos.
---
# PARTE 1 -- El problema de parsear a mano
## 1.1 El parser manual: tu primer intento (y por qué dejarlo)
Imaginate que querés hacer un programa al que se le puedan pasar opciones como estas:
```bash
./miapp --help -v --max=42 archivo.txt
```
Tu primer impulso es escribir algo así:
```c
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) { /* ... */ }
else if (strcmp(argv[i], "-v") == 0) { /* ... */ }
else if (strncmp(argv[i], "--max=", 6) == 0) { /* ... */ }
}
```
Esto funciona para tres opciones. Pero mirá lo que viene:
- ¿Qué pasa si `-v` puede ir con valor (`-v 5`) **y** sin él (`-v`)?
- ¿Cómo hacés para soportar opciones apiladas (`-abc` = `-a -b -c`)?
- ¿Y si el usuario escribe `--max=abc`, `--MAX`, `--max `, o simplemente `--max` al final?
- ¿Dónde separás las opciones de los archivos posicionales?
Cada opción nueva duplica tu código. Cada caso de error lo escribís a mano. Es un trabajo de hormiga
que **ya alguien hizo por vos**.
> **Para guardar.** Esto no es una crítica a tu capacidad: es un reconocimiento a que `getopt_long` fue
> escrito por personas que saben que repetimos el mismo patrón en cada programa. **Delegatemos lo
> aburrido.**
## 1.2 La alternativa: delegar en `getopt_long`
`getopt_long()` es una función estándar POSIX que recibe los argumentos de `main`, busca las opciones
que vos le pediste, y te devuelve **una por una** hasta agotarlas. Vos solo escribís un `switch` con
lo que querés hacer con cada opción; el resto lo resuelve la función:
- opciones apiladas (`-abc`)
- valores pegados (`--max=42`) y separados (`--max 42`)
- detección de opciones desconocidas
- aviso de valores faltantes
- reordenamiento interno (deja los posicionales juntos al final)
Todo eso, en **una sola llamada**. La contraparte corta (`getopt`) solo entiende `-x`. `getopt_long`
entiende **tanto** `-x` como `--opcion-larga`. Eso es todo lo que importa para este capítulo.
## Práctica de la Parte 1
### Ejercicio 13.1 -- Contar líneas de código manuales
**Conceptos** · parseo manual, comparación de cadenas, complejidad
**Contexto** · Antes de ver la solución, necesitás sentir **cuánto trabajo** evita `getopt_long`.
Simplificación: no escribís nada. Solo calculás.
**Consigna** · Dado este conjunto de opciones, ¿cuántas comparaciones individuales (`strcmp` o
`strncmp`) tendrías que escribir para reconocerlas **todas** con el método manual?
| Opción | Requiere valor? | Formas aceptadas |
|---|---|---|
| `-h` / `--help` | no | `-h`, `--help` |
| `-v` / `--verbose` | no | `-v`, `--verbose` |
| `-o` / `--output` | sí | `-o f`, `-of`, `--output=f`, `--output f` |
| `-m` / `--max` | sí | `-m n`, `-mn`, `--max=n`, `--max n` |
Contá cada comparación individual que tendrías que escribir en tu `if/else`. No uses `getopt_long`.
Solo pensá.
**Entrada** · Ninguna (ejercicio de análisis).
**Salida** · El número total de comparaciones y una lista breve de por qué son necesarias.
**Criterios de aceptación**
- [ ] Contaste cada forma individual, no cada opción.
- [ ] Consideraste pegado con `=` y separado por espacio como formas distintas.
- [ ] Reconociste que `-of` necesita recorrer carácter por carácter.
---
# PARTE 2 -- La llamada a `getopt_long`
## 2.1 La firma completa
Acá está la función. Memorizá la forma; después desarmamos cada pieza.
```c
int getopt_long(int argc,
char *const argv[],
const char *optstring,
const struct option *longopts,
int *longindex);
```
Pero en la práctica casi siempre la llamás así:
```c
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "hvo:m:",
long_options, &option_index)) != -1) {
switch (opt) {
case 'h': /* -h o --help */ break;
case 'v': /* -v o --verbose */ break;
case 'o': /* -o o --output */ break;
case 'm': /* -m o --max */ break;
case '?': /* error */ break;
}
}
```
Esa es **toda** la estructura. Una sola vez declarás las opciones (en `long_options` y en el
`optstring`). Después, el bucle se repite automáticamente hasta que devuelve `-1`, y en cada vuelta te
dice cuál opción encontró a través de `opt`.
## 2.2 Desglose parámetro por par
| Parámetro | Qué es | Qué le pasás | Por qué importa |
|---|---|---|---|
| `argc` | Cantidad de argumentos | El mismo `argc` de `main` | Necesita la lista cruda |
| `argv` | Arreglo de cadenas | El mismo `argv` de `main` | Recorre los argumentos por vos |
| `optstring` | Opciones cortas (ver Parte 3) | `"hvo:m:"` | Dice cuáles son cortas y cuáles llevan valor |
| `longopts` | Tabla de opciones largas (ver Parte 4) | Nombre de tu array de `struct option` | Dice qué nombres largos reconoce |
| `longindex` | Puntero a int (opcional, podés poner `NULL`) | `&option_index` | Si lo pasás, te dice en qué posición de la tabla encontró la opción |
El **retorno** de cada llamada:
- Un `char` convertido a `int`: el código de la opción encontrada (`'h'`, `'v'`, `'o'`, `'m'`).
- `'?'` si la opción es desconocida o le falta un valor requerido.
- `-1` cuando ya no quedan opciones: el bucle se rompe.
> **Ojo con esto.** El retorno es `int` (no `char`) porque necesitás representar `-1` junto con los
> códigos de opción. Si lo hubieran devuelto como `char`, en muchos sistemas `-1` y `'ÿ'` sería el
> mismo valor, y no podrías distinguirlos. Por eso la variable se llama `opt` (un `int`) y **nunca**
> `char opt`.
## 2.3 El patrón que se repite siempre
Cada programa que use `getopt_long` sigue **exactamente el mismo esqueleto**:
```c
#include <getopt.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int opt;
int option_index = 0;
/* Declará las opciones largas ANTES del bucle. */
static struct option long_options[] = { ... };
/* Bucle: getopt_long devuelve la opción en cada vuelta. */
while ((opt = getopt_long(argc, argv, "hvo:m:",
long_options, &option_index)) != -1) {
switch (opt) {
case 'h': break; /* una opción */
case 'v': break; /* otra opción */
case '?': break; /* error */
}
}
return 0;
}
```
Fijate que **el bucle y el switch son siempre iguales**. Lo único que cambia entre un programa y otro es:
1. El contenido de `long_options` (qué opciones largas reconocés).
2. El string de opciones cortas (el tercer argumento de `getopt_long`).
3. Los cuerpos de los `case`.
> **Momento ajá.** Una vez que dominás este esqueleto, podés copiarlo a cualquier proyecto y solo
> cambiar las opciones. Es como un molde: la forma no cambia, solo el contenido.
## Práctica de la Parte 2
### Ejercicio 13.2 -- Leer la firma sin compilar
**Conceptos** · parámetros de `getopt_long`, tipos
**Contexto** · Antes de correr código, necesitás saber **qué recibe** y **qué devuelve**. Simplificación:
interpretar una llamada a `getopt_long`.
**Consigna** · Dada esta llamada:
```c
while ((opt = getopt_long(argc, argv, "hvfo:", my_options, NULL)) != -1) { ... }
```
respondé sin compilar:
1. ¿Cuántas opciones **cortas** se están declarando? ¿Cuáles son?
2. ¿Cuáles llevan valor (y cuáles no)? Mirá el `optstring`.
3. ¿Se están usando las opciones largas? ¿Cómo lo sabés?
4. ¿Qué pasa si le pasás `NULL` como último parámetro (`longindex`)? ¿Ocurre algo malo?
**Entrada** · La línea de llamada mostrada arriba.
**Salida** · Las cuatro respuestas.
**Criterios de aceptación**
- [ ] Identificás las opciones cortas en el string.
- [ ] Distinguís cuáles necesitan valor por el `:` siguiente.
- [ ] Sabés que `NULL` en `longindex` es válido: simplemente no se llena la información del índice.
- [ ] El `my_options` es un array de `struct option`, aunque no lo veas declarado acá.
### Ejercicio 13.3 -- Completar el esqueleto
**Conceptos** · estructura básica, variables globales necesarias
**Contexto** · Vos tenés que escribir las variables que faltan para que el esqueleto compile (aunque
no haga nada). Simplificación: declarar lo mínimo para compilar un programa con `getopt_long`.
**Consigna** · Escribí la parte principal de un programa C (desde `main` hasta el cierre) que llame a
`getopt_long` con estas opciones:
- Cortas: `-h`, `-v`, `-d <archivo>`
- Largas: `--help`, `--verbose`, `--debug-file` (esta última requiere un valor como archivo)
El cuerpo de cada `case` puede estar vacío. Lo importante es que las **declaraciones** sean correctas.
**Criterios de aceptación**
- [ ] Se incluye `<getopt.h>`.
- [ ] El `optstring` corto es `"hvd:"`.
- [ ] El array de `struct option` tiene filas para `help`, `verbose` y `debug-file`.
- [ ] Cada fila larga declara correctamente si lleva o no argumento.
- [ ] El bucle es `while ((opt = getopt_long(...)) != -1)`.
---
# PARTE 3 -- Los tres macros que definen "si lleva valor"
## 3.1 ¿Qué significa "argumento" en este contexto?
Antes de leer la tabla, una aclaración importante: cuando `getopt_long` habla de **argumento**, no se
refiere a los argumentos de `main` (`argc`/`argv`). Se refiere a **si la opción lleva consigo un valor
adicional**.
- `-v` → **no** lleva nada. Es un interruptor: prende o apaga algo.
- `-d archivo.txt` → **sí** lleva un valor adicional: `"archivo.txt"`.
Ese "sí" o "no" es exactamente lo que dicen los macros de esta parte.
## 3.2 Los tres valores posibles
Cada fila de tu tabla de `struct option` necesita una respuesta a esta pregunta: **¿la opción larga
lleva valor?** Las opciones son tres:
| Macro | Significa | Ejemplo | ¿Se usa para? |
|---|---|---|---|
| `no_argument` | La opción **no** lleva valor (es un flag) | `--help`, `-v` | Interruptores booleanos |
| `required_argument` | La opción **sí** lleva valor | `--output=archivo`, `-d archivo` | Valores obligatorios |
| `optional_argument` | La opción **puede** llevar valor, pero no es obligatorio | `--mode=fast` (ó simplemente `--mode`) | Valores opcionales con sentido por defecto |
Solo estos tres. No hay un cuarto. Si una opción lleva algo adicional, es uno de los dos primeros.
Si puede ir sin nada, es el tercero.
**Analogía.** Pensá en las opciones como **interruptores de luz**:
- `no_argument` → un interruptor normal: prendé/apagá, punto.
- `required_argument` → un dimmer (regulador): necesitás decir **hasta dónde** querés bajar la luz.
- `optional_argument` → un dimmer con preset: si lo tocás solo prende al nivel medio; si lo girás,
elegís el nivel vos.
## 3.3 El truco de los nombres
Los macros se llaman `no_argument`, `required_argument` y `optional_argument`. Notá que **argument**
lleva una "u" --es del inglés. En el contexto de esta función, "argument" = "valor adicional de la
opción". Nunca te confundas con `argc`/`argv`: son cosas distintas.
> **Para guardar.** Cuando veas `required_argument`, leelo como **"esta opción lleva un valor"** --no
> como "requiere un argumento de función", que es otra cosa totalmente.
## Práctica de la Parte 3
### Ejercicio 13.4 -- Clasificar opciones por su macro
**Conceptos** · `no_argument`, `required_argument`, `optional_argument`
**Contexto** · Antes de escribir código, necesitás **leer** una lista de opciones y saber qué macro
corresponde a cada una. Simplificación: solo elegís el macro correcto.
**Consigna** · Para cada opción, escribí qué macro corresponde en la tabla de `struct option`:
| Opción | ¿Lleva valor adicional? | Macro correcto |
|---|---|---|
| `--help` | no | ? |
| `--max 10` | sí, el número | ? |
| `--output=fichero.txt` | sí, el nombre de archivo | ? |
| `--verbose` (solo activa flag) | no | ? |
| `--color` (si acepta `--color=auto` ó solo `--color`) | puede tener o no | ? |
**Entrada** · Las opciones listadas arriba.
**Salida** · La tabla completada con los macros correctos.
**Criterios de aceptación**
- [ ] `no_argument` para las que son puro interruptor (flag).
- [ ] `required_argument` para las que **siempre** llevan un valor.
- [ ] `optional_argument` para la que puede ir con o sin valor.
---
# PARTE 4 -- El struct de opciones largas
## 4.1 ¿Qué es `struct option`?
Cada fila de tu tabla es una estructura de cuatro campos:
```c
struct option {
const char *name; /* el nombre SIN los guiones */
int has_arg; /* no_argument, required_argument u optional_argument */
int *flag; /* NULL si querés comportamiento estándar */
int val; /* código corto que aparece en el switch */
};
```
Y lo usás así:
```c
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"output", required_argument, 0, 'o'},
{"max", required_argument, 0, 'm'},
{0, 0, 0, 0} /* TERMINADOR */
};
```
La **última fila es obligatoria**: `{0, 0, 0, 0}`. Sin ella, `getopt_long` no sabe dónde termina tu
tabla y va a leer memoria de más --potencialmente un crash o comportamiento impredecible.
## 4.2 Desglose de cada campo
### Campo 1: name -- el nombre largo sin los guiones
```c
{"help", ...} /* corresponde a --help */
{"verbose", ...} /* corresponde a --verbose */
```
Acá escribís **solo la palabra**, sin los guiones. `getopt_long` sabe que si la tabla contiene
`"help"`, eso significa `--help`. Nunca ponés los guiones en este campo.
### Campo 2: has_arg -- ¿lleva valor?
Usás uno de los tres macros de la Parte 3:
```c
{"help", no_argument, 0, 'h'}, /* --help --> flag, sin valor */
{"max", required_argument, 0, 'm'}, /* --max=10 o --max 10 --> lleva un valor */
```
### Campo 3: flag -- lo que va a pasar con el retorno (casi siempre 0)
Este es el campo que más confusión genera. Tiene dos modos:
**Modo A (el que vas a usar el 99% de las veces): `flag = 0`.**
En este modo, `getopt_long` devuelve en cada vuelta el valor del cuarto campo (`val`). Así,
`--help` devuelve `'h'`, que coincide con `-h`, y ambos van al mismo `case 'h':`.
```c
{"help", no_argument, 0, 'h'}, /* devuelve 'h' (como -h) */
```
**Modo B: `flag` apunta a una variable.** En este caso, `getopt_long` **no** usa el cuarto campo. En vez de
devolver un código, escribe `1` en la variable apuntada por `flag` y devuelve `0`. Es útil para opciones
largas que no tienen equivalente corto, pero es un patrón avanzado que no vamos a usar acá.
Para este capítulo, **siempre poné `0`**.
### Campo 4: val -- el código que aparece en tu switch
Este es el valor de retorno cuando se encuentra la opción larga. Lo más común es que sea igual al
código de la opción corta correspondiente:
```c
{"help", no_argument, 0, 'h'}, /* --help devuelve 'h' */
{"verbose", no_argument, 0, 'v'}, /* --verbose devuelve 'v' */
```
Así, `--help` y `-h` caen en el **mismo** `case 'h':`. Escribís la lógica una vez y sirve para los dos.
> **Para guardar.** El cuarto campo es el **puente**: une la opción larga con el código corto del
> `switch`. Si pones `'h'`, ambas variantes (`-h` y `--help`) van al mismo `case`.
## 4.3 El terminador: {0, 0, 0, 0}
Esta fila dice a `getopt_long`: "acá terminó mi tabla". Es un arreglo, no una lista con un conteo
adjunto --la única forma de saber dónde termina es encontrar ceros. Si la olvidás:
- `getopt_long` lee memoria más allá del array → valores basura.
- Probablemente termine en un crash o en comportamientos imposibles de diagnosticar.
- El error **no se detecta** en tiempo de compilación: aparece solo al ejecutar (si tenés suerte).
**No la olvides. Nunca.** La ponés siempre, sin excepciones, incluso si tu tabla tiene una sola opción.
## 4.4 Uniendo las partes: un ejemplo completo de tabla
```c
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"debug", optional_argument, 0, 'd'},
{"output", required_argument, 0, 'o'},
{"max-retries", required_argument, 0, 'm'},
{0, 0, 0, 0}
};
```
Notá que `"max-retries"` tiene guión medio (`-`). Eso es normal: el nombre de la opción larga puede
llevar cualquier carácter que no sea `-`. Y sí, `getopt_long` entiende exactamente esa palabra como
`--max-retries`.
> **Ojo con esto.** La tabla solo define **opciones largas**. Para las opciones cortas (`-h`, `-v`),
> necesitás también el `optstring` (el string del tercer parámetro de `getopt_long`). Si ponés una
> opción larga en la tabla pero no la incluís en el `optstring`, igual funciona. Pero si ponés una
> corta en el `optstring` y no en la tabla, solo funciona como corta (obvio).
## Práctica de la Parte 4
### Ejercicio 13.5 -- Escribir la tabla de opciones
**Conceptos** · `struct option`, los cuatro campos, terminador
**Contexto** · La tabla de opciones es el corazón de `getopt_long`: sin ella, no hay opciones largas.
Simplificación: escribir una tabla correcta para un conjunto dado de opciones.
**Consigna** · Escribí la declaración completa de `long_options` (array de `struct option`) para un
programa que acepta:
| Opción corta | Opción larga | ¿Lleva valor? |
|---|---|---|
| `-h` | `--help` | no |
| `-v` | `--verbose` | no |
| `-c` | `--config-file` | sí (nombre de archivo) |
| `-t` | `--timeout` | sí (número en segundos) |
No escribís la llamada a `getopt_long` ni el bucle. Solo la tabla.
**Entrada** · La tabla de opciones arriba.
**Salida** · El código C completo para declarar el array.
**Identificadores sugeridos** · `long_options`
**Criterios de aceptación**
- [ ] Cada fila tiene los cuatro campos en orden.
- [ ] `name` no incluye los guiones (`"help"`, no `"--help"`).
- [ ] El segundo campo usa el macro correcto para cada opción.
- [ ] La última fila es `{0, 0, 0, 0}`.
- [ ] Los valores del cuarto campo coinciden con las letras de las opciones cortas.
### Ejercicio 13.6 -- Debuggear una tabla rota
**Conceptos** · errores comunes en `struct option`
**Contexto** · Las tablas mal escritas son la fuente más común de bugs con `getopt_long`. Simplificación:
encontrar los errores en una tabla dada.
**Consigna** · Esta tabla tiene **tres errores**. Encontralos y escribí qué está mal en cada fila:
```c
static struct option long_options[] = {
{"help", required_argument, 0, 'h'},
{"verbose", no_argument, 0, "v"},
{"output", required_argument, NULL, 'o'},
{"max", no_argument, 0, 'm'}
};
```
**Entrada** · La tabla con errores arriba.
**Salida** · Tres errores identificados, uno por fila (la última fila es correcta).
**Criterios de aceptación**
- [ ] Identificás el `has_arg` incorrecto en `help`.
- [ ] Encontrás que `"v"` es una cadena, no un char.
- [ ] Reparás el `flag = NULL` cuando debería ser `0`.
- [ ] Reconocés que la fila de `max` está correcta (es un distractor).
---
# PARTE 5 -- Las variables globales: lo que `getopt_long` deja atrás
## 5.1 La analogía del cajero automático
Cuando vas a un cajero, le pasás tus datos y él **guarda información internamente** para el siguiente
paso: tu número de cuenta, la fecha límite, etc. Vos no necesitás recordarlo --el cajero lo tiene en
mente y te lo va preguntando.
`getopt_long` hace exactamente eso con tres **variables globales**: las modifica para que vos puedas
consultarlas cuando necesités un dato adicional. Estas variables están declaradas en `<getopt.h>`;
no las declarás vos, simplemente las usás.
## 5.2 optarg: el valor de la opción
Cada vez que `getopt_long` encuentra una opción que **requiere un valor** (`-o archivo.txt`,
`--output archivo.txt` o `--output=archivo.txt`), guarda ese valor en la variable global `optarg`.
```c
while ((opt = getopt_long(argc, argv, "o:m:", ...)) != -1) {
switch (opt) {
case 'o':
printf("Archivo de salida: %s\n", optarg); /* <-- aca esta! */
break;
case 'm':
printf("Valor maximo: %s\n", optarg); /* <-- tambien aca */
break;
}
}
```
`optarg` es un `char *`. Puntero a cadena. Siempre contiene **lo que viene después** de la opción, ya
sea pegado con `=` o separado por espacio. No necesitás hacer aritmética de punteros: `getopt_long`
ya lo resolvió.
> **Momento ajá.** Esto es exactamente lo que resolvíamos a mano en el capítulo anterior:
> sumar un offset al puntero para encontrar el valor después del `=`. Acá, la función lo hace por vos y
> te lo deja en una variable lista para usar.
## 5.3 optind: donde empiezan los posicionales
`getopt_long` recorre `argv`, consumiendo todas las opciones que encuentra (cortas y largas). Cuando
termina, la variable global `optind` apunta al **primer elemento de `argv` que NO es opción**: el primer
argumento posicional.
```c
while ((opt = getopt_long(argc, argv, "hv:", ...)) != -1) {
/* procesar opciones */
}
/* Aca optind == indice del primer argumento posicional */
printf("Posicionales:");
for (int i = optind; i < argc; i++) {
printf(" %s", argv[i]);
}
```
Podés **consultar** `optind` en cualquier momento. Tampoco lo modificás vos; `getopt_long` lo va
avanzando automáticamente a medida que consume opciones.
> **Truco de laboratorio.** Podés verificar que `optind` funciona bien con este comando simple:
> ```bash
> ./miapp -v --max=10 archivo.txt salida.txt
> ```
> Cuando `getopt_long` termine, `optind` debería valer `3`, porque los posicionales empiezan en
> `argv[3]` (`"archivo.txt"`).
## 5.4 optopt: la opcion desconocida
Cuando `getopt_long` encuentra una opción corta **que no está en el `optstring`**, guarda esa letra en
`optopt`. Es útil para mostrar un mensaje de error más preciso:
```c
case '?':
fprintf(stderr, "Error: opcion desconocida '-%c'\n", optopt);
break;
```
Si la opción desconocida es larga (`--desconocida`), `optopt` vale `0`. En ese caso, el mensaje de
error por defecto de `getopt_long` (que ya imprime algo como `./prog: unrecognized option '--desconocida'`)
ya es suficientemente claro.
## 5.5 getopt_reset(): reiniciar todo
Si necesitás pasar **otra vez** por los argumentos (por ejemplo, si llamaste a una función que también
usa `getopt_long`), podés llamar a:
```c
getopt_reset();
```
Esto resetea `optind`, `optarg`, y el estado interno para que la próxima llamada a `getopt_long` vuelva
a recorrer desde el comienzo. En Windows con MSVC puro no está disponible (MinGW sí lo tiene). Si tu
codigo corre en MSVC, usá esta alternativa:
```c
optind = 0;
```
Es un hack sucio pero que funciona. `getopt_reset()` es solo eso: resetear `optind` a cero para que la
próxima llamada recalcule donde empieza.
## 5.6 Por que globales? No es un bug, es un diseño
Las variables globas te pueden generar incomodidad: *Que pasa si dos hilos llaman a `getopt_long` al
mismo tiempo?* --nada, porque `getopt_long` no es thread-safe y punto. Las herramientas de consola
suelen ser monohebra. Usar globas para estas tres variables simplifica la API: no necesitás pasar un
puntero a contexto por cada llamada; simplemente lo consultás cuando querés.
Es un intercambio clasico de diseno: **API mas simple** ↔ **no thread-safe**. Para una herramienta de
consola, es el cambio correcto.
## Práctica de la Parte 5
### Ejercicio 13.7 -- Completar el uso de variables globales
**Conceptos** · `optarg`, `optind`, `optopt`
**Contexto** · Dada una llamada a `getopt_long` con un esqueleto incompleto, completar las líneas que
usan las variables globales para mostrar los valores de las opciones. Simplificación: el programa es
minimo; solo usa `optarg`.
**Consigna** · Completá las líneas marcadas con `/* ??? */` usando las variables globales correctas:
```c
#include <getopt.h>
#include <stdio.h>
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"output", required_argument, 0, 'o'},
{"verbose", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
int main(int argc, char *argv[])
{
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "ho:", long_options, &option_index)) != -1) {
switch (opt) {
case 'h':
printf("Usando ayuda.\n");
break;
case 'o':
printf("Archivo: ???\n"); /* que variable tiene el valor? */
break;
case 'v':
printf("Verbose activo.\n");
break;
}
}
printf("Primer posicional: ???\n"); /* donde empiezan los posicionales? */
return 0;
}
```
**Entrada** · El código incompleto arriba.
**Salida** · Las dos líneas completadas con la variable global correcta y una breve justificación.
**Criterios de aceptación**
- [ ] Completaste `'o'` usando `optarg`.
- [ ] Completaste el posicional usando `argv[optind]`.
- [ ] Justificaste por qué cada variable es la correcta.
### Ejercicio 13.8 -- Que vale optind despues de getopt_long?
**Conceptos** · `optind`, recorrido interno
**Contexto** · Entender **qué pasa adentro** cuando corré `getopt_long`. Simplificación: sin compilar,
adivinar el valor de `optind` después de que `getopt_long` termina.
**Consigna** · Para cada invocación, escribí cuánto vale `optind` cuando `getopt_long` termina:
1. `./prog -v --max=10 archivo.txt salida.txt`
2. `./prog --help`
3. `./prog -hvo file.txt`
**Entrada** · Las tres invocaciones arriba.
**Salida** · Tres valores de `optind`.
**Criterios de aceptación**
- [ ] Para la invocación 1, identificás que las opciones consumidas son `-v`, `--max=10`.
- [ ] Para la invocación 2, identificás que `--help` se consume y no quedan opcionales.
- [ ] Para la invocación 3, entendés que `-hvo` consume tres letras cortas.
---
# PARTE 6 -- Errores y el caso `'?'`
## 6.1 Qué pasa cuando algo sale mal
`getopt_long` no deja que los errores pasen desapercibidos. Si el usuario hace algo incorrecto, la
función **devuelve `'?'`** (el carácter punto de interrogación, valor ASCII 63) en lugar del código
de una opción válida.
Hay tres situaciones que generan un retorno `'?'`:
| Situación | Ejemplo | Qué muestra `getopt_long` por defecto |
|---|---|---|
| Opción corta desconocida | `-x` (si `-x` no está en el optstring) | `./prog: invalid option -- 'x'` |
| Opción larga desconocida | `--desconocida` | `./prog: unrecognized option '--desconocida'` |
| Valor faltante | `--max` sin valor (cuando requiere uno) | `./prog: option '--max' requires an argument` |
En todos los casos, el retorno es `'?'`. Tu trabajo es manejarlo con estilo.
## 6.2 El caso `'?'` en el switch
El patrón estándar es simple:
```c
case '?':
/* Opción desconocida o valor faltante */
fprintf(stderr, "Uso: %s [opciones] <archivo>\n", argv[0]);
return 1;
```
Es un **error de entrada**: el usuario le pasó algo que tu programa no entiende. La respuesta correcta
es **mostrar un mensaje de uso** y **terminar con código de error** (`return 1`).
> **Ojo con esto.** `getopt_long` **ya imprimió** su propio mensaje de error por `stderr` antes de
> devolver `'?'`. Si volvés a imprimir el mismo error, vas a tener dos líneas. Podés:
>
> 1. Aceptar los dos mensajes (simplón).
> 2. Anular el mensaje de `getopt_long` implementando una función de error personalizada (avanzado, no lo necesitamos acá).
> 3. Ignorar el segundo mensaje y simplemente mostrar la ayuda (recomendado para este capítulo).
## 6.3 El case default: por qué ponerlo siempre
Aunque ya cubriste todos los casos, poné siempre un `default:` en tu switch:
```c
switch (opt) {
case 'h': /* ... */ break;
case 'v': /* ... */ break;
case 'o': /* ... */ break;
case '?': /* error */ break;
default: /* nunca se llega aqui, pero esta aca por seguridad */ break;
}
```
Es una red de seguridad. Si en el futuro agregás un `case` nuevo y te olvidás de uno, al menos el
programa **no se compila** (si tenés `-Wdefault` o similar). Con el `default`, la compilación pasa
tranquila, sabiendo que cubriste todo.
## Práctica de la Parte 6
### Ejercicio 13.9 -- Manejar errores con `'?'`
**Conceptos** · caso `'?'`, `stderr`, códigos de salida
**Contexto** · Un programa que usa `getopt_long` necesita manejar el caso de opciones inválidas sin
crashear. Simplificación: detectar la opcion desconocida y mostrar una ayuda.
**Consigna** · Completá el esqueleto siguiente para que maneje correctamente el caso `'?'`: cuando
`getopt_long` devuelve `'?'`, el programa debe mostrar un mensaje de error por `stderr` con la línea
de uso, e **inmediatamente retornar 1**.
```c
#include <getopt.h>
#include <stdio.h>
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"output", required_argument, 0, 'o'},
{0, 0, 0, 0}
};
int main(int argc, char *argv[])
{
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "ho:", long_options, &option_index)) != -1) {
switch (opt) {
case 'h':
printf("Uso: %s [-h] [-o archivo]\n", argv[0]);
return 0;
case 'o':
printf("Archivo: %s\n", optarg);
break;
case '?':
/* COMPLETAR AQUI */
;
default:
break;
}
}
return 0;
}
```
**Entrada** · El código incompleto arriba.
**Salida** · El `case '?'` completado con el mensaje por `stderr` y el `return 1`.
**Criterios de aceptación**
- [ ] Escribís un `fprintf(stderr, ...)` con la línea de uso.
- [ ] Incluís `return 1;` inmediatamente después del `fprintf`.
- [ ] El código termina sin llegar al `default:` en caso de error.
---
# PARTE 7 -- Posicionales despues de optind
## 7.1 Los posicionales son lo que queda
Llegó el momento más sencillo: una vez que `getopt_long` terminó de procesar todas las opciones
(cortas y largas), los **argumentos posicionales** (archivos, nombres, datos "de verdad") quedan
acumulados al final de `argv`, empezando en la posición `optind`.
```c
/* despues de que el while termina, optind apunta al primer posicional */
for (int i = optind; i < argc; i++) {
printf("Procesando: %s\n", argv[i]);
}
```
Es un bucle simple. No hay nada especial que hacer. `getopt_long` se encargó de **separar** las
opciones (que consumió una por una) de los posicionales (que dejó intactos al final).
## 7.2 La convencion de -- (fin de opciones)
Existe una convención en la línea de comandos: un argumento que es **solo** `--` le dice a cualquier
programa POSIX: *"de acá en adelante, todo es posicional, aunque empiece con guión"*.
```bash
./prog -- -archivo-que-empieza-con-guiones.txt
```
Acá, `-archivo-que-empieza...` **no** se interpreta como una opción porque `--` ya marcó el límite.
`getopt_long` respeta esta convención automáticamente. Cuando ve un `--` solo, deja de buscar opciones
y devuelve `-1`, dejando todo lo que sigue para `optind`.
## 7.3 Un ejemplo completo del flujo
```c
#include <getopt.h>
#include <stdio.h>
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"output", required_argument, 0, 'o'},
{0, 0, 0, 0}
};
int main(int argc, char *argv[])
{
int opt;
int option_index = 0;
int verbose = 0;
const char *output_file = NULL;
/* Primero: procesar todas las opciones */
while ((opt = getopt_long(argc, argv, "ho:", long_options, &option_index)) != -1) {
switch (opt) {
case 'h':
printf("Uso: %s [-hv] [-o archivo] <archivos>...\n", argv[0]);
return 0;
case 'v':
verbose = 1;
break;
case 'o':
output_file = optarg;
break;
case '?':
fprintf(stderr, "Uso: %s [-hv] [-o archivo] <archivos>...\n", argv[0]);
return 1;
}
}
/* Segundo: procesar los posicionales (lo que quedaba) */
for (int i = optind; i < argc; i++) {
if (verbose) printf("Procesando: %s\n", argv[i]);
/* ... aqui iria la logica real de procesamiento ... */
}
return 0;
}
```
Acá tenés el flujo completo en acción: primero opciones, luego posicionales. El mismo patrón se repite
en **todos** los programas que usan `getopt_long`.
## Práctica de la Parte 7
### Ejercicio 13.10 -- Listar los posicionales despues de getopt_long
**Conceptos** · `optind`, bucle posicional, `argc`
**Contexto** · Dado un programa que procesó opciones con `getopt_long`, listar los archivos que quedaron.
Simplificación: solo imprimirlos, sin procesar.
**Consigna** · Completá la parte final del siguiente programa para que liste todos los argumentos
posicionales (desde `optind` hasta `argc-1`) después de que termina el bucle de opciones:
```c
#include <getopt.h>
#include <stdio.h>
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"verbose", no_argument, 0, 'v'},
{"max", required_argument, 0, 'm'},
{0, 0, 0, 0}
};
int main(int argc, char *argv[])
{
int opt;
int option_index = 0;
int verbose = 0;
int max = 0;
while ((opt = getopt_long(argc, argv, "hvm:", long_options, &option_index)) != -1) {
switch (opt) {
case 'h':
printf("Uso: %s [-hv] [-m N] <archivos>...\n", argv[0]);
return 0;
case 'v':
verbose = 1;
break;
case 'm':
max = atoi(optarg);
break;
case '?':
fprintf(stderr, "Uso: %s [-hv] [-m N] <archivos>...\n", argv[0]);
return 1;
}
}
/* COMPLETAR: listar los posicionales despues de este comentario */
return 0;
}
```
El formato de salida para cada posicional debe ser: `Archivo N: <nombre>`, donde `N` es un contador
que empieza en 1.
**Entrada** · El código incompleto arriba.
**Salida** · El bucle completado que lista los posicionales.
**Criterios de aceptación**
- [ ] El bucle recorre desde `optind` hasta `argc - 1`.
- [ ] Cada posicional se imprime con el formato correcto.
- [ ] El contador empieza en 1, no en 0.
### Ejercicio 13.11 -- Unir opciones y posicionales
**Conceptos** · flujo completo getopt_long + optind, variables locales
**Contexto** · Escribir un programa minimo pero completo que use `getopt_long` con una opcion corta y
larga (con valor), procese los posicionales despues, e imprima un resumen. Simplificacion: no
procesar ningun archivo de verdad, solo reportar lo que recibio.
**Consigna** · Escribí el programa completo desde cero. Debe aceptar:
| Corta | Larga | Valor? | Accion |
|---|---|---|---|
| `-h` | `--help` | no | Muestra la ayuda y termina con 0. |
| `-n` | `--name` | si | Guarda el nombre del trabajo. |
Y despues de procesar opciones, lista los posicionales (si hay). El formato de salida debe ser:
```text
Trabajo: <nombre>
Opciones leídas: -n/-verbose, --help=0
Archivos a procesar: <cantidad>
Archivo 1: <nombre>
Archivo 2: <nombre>
```
**Criterios de aceptación**
- [ ] Incluye `<getopt.h>` y declara correctamente `long_options`.
- [ ] El optstring es `"hn:"`.
- [ ] La tabla tiene dos filas con terminador.
- [ ] `-h`/`--help` muestra ayuda y retorna 0.
- [ ] `-n`/`--name` guarda el valor de `optarg` en una variable local.
- [ ] El caso `'?'` muestra error por `stderr` y retorna 1.
- [ ] Despues del bucle, recorre desde `optind` hasta `argc` listando los posicionales.
- [ ] El formato de salida coincide con el ejemplo.
---
# PARTE 7.5 -- La ayuda de uso: imprimir una guia clara
## 7.5.1 No hay funcion estandar en C para esto
Acá va la primera noticia: **C no tiene ninguna función estándar que genere automáticamente**
una pantalla de ayuda formateada. Ni `printf`, ni `getopt_long`, ni nada en `<stdio.h>` o `<getopt.h>`
lo hace por vos. Cada programa imprime su propia ayuda manualmente con `fprintf(stderr, ...)` o
`printf(...)`.
Eso **no es un defecto**: es flexibilidad. Algunas herramientas muestran una ayuda corta de una línea;
otras, una pantalla completa con descripciones detalladas. Vos decis cómo se ve.
Lo que sí existe es un **formato estándar de facto** que usan casi todas las herramientas POSIX —`ls`,
`grep`, `tar`, `curl`— y que el usuario ya reconoce sin pensar. Aprender ese formato es tan importante
como aprender a usar `getopt_long`.
## 7.5.2 El formato estandar: tu ejemplo como plantilla
Este es el formato canónico que vas a seguir. Es el mismo que usás en tu ejemplo, con ligeras mejoras:
```text
Uso: mi_programa [opciones] <archivo_origen> <archivo_destino>
Argumentos posicionales:
archivo_origen Ruta del archivo que se va a leer (obligatorio, posición 1).
archivo_destino Ruta del archivo donde se guardará el resultado (obligatorio, posición 2).
Opciones:
-h, --help Muestra esta ayuda y termina.
-v, --verbose Activa el modo detallado.
```
Desglosémoslo por secciones.
### La línea de Uso
```
Uso: mi_programa [opciones] <archivo_origen> <archivo_destino>
```
Esta primera línea dice **todo** en un vistazo. Sigue reglas fijas de notación:
| Notacion | Significado | Ejemplo |
|---|---|---|
| `[opcionales]` | Lo que va entre corchetes es **opcional** —se puede omitir. | `[-hv] [-o archivo]` |
| `<obligatorio>` | Los angulares indican un argumento **obligatorio**. | `<archivo_origen>` |
| `palabra` | Sin nada: lo que se escribe tal cual, como `--help`. | `--verbose` |
La estructura típica es siempre:
```
Uso: <nombre_programa> [opciones] <posicionales_obligatorios>
```
NUNCA uses corchetes para envolver todo el uso —eso confunde. La línea debe ser tan clara que alguien
la lea una vez y sepa cómo ejecutar el programa.
### Argumentos posicionales
La sección de posicionales va **primero** (antes de las opciones) porque son la parte principal del
programa: sin ellos, nada tiene sentido. Cada posicional lleva:
1. El nombre del argumento (en `snake_case`, minúsculas).
2. Un tabulador o dos espacios.
3. La descripción.
4. Entre paréntesis al final: si es obligatorio u opcional, y su posición numérica.
```text
Argumentos posicionales:
archivo_origen Ruta del archivo que se va a leer (obligatorio, posición 1).
archivo_destino Ruta del archivo donde se guardará el resultado (obligatorio, posición 2).
modo Modo de procesamiento: "normal" o "agresivo" (opcional, posición 3, valor por defecto: "normal").
```
### Opciones
La sección de opciones va **después**. Cada opción muestra la forma corta primero, luego la larga,
separadas por coma. La descripción comienza en la misma columna que todas las demás descripciones:
```text
Opciones:
-h, --help Muestra esta ayuda y termina.
-v, --verbose Activa el modo detallado.
-o, --output <archivo> Guarda el resultado en el archivo indicado.
-m, --max <N> Número máximo de elementos a procesar (por defecto: 100).
```
Notá que las opciones con valor incluyen `<nombre_del_valor>` entre la opción y la descripción.
Las opciones sin valor van directo a la descripción.
## 7.5.3 Un helper para no repetir codigo
Aunque no hay función estándar, el patrón mas usado es crear una funcion propia llamada `print_help`
que recibirá el nombre del programa (generalmente `argv[0]`) y escribirá toda la ayuda de golpe.
Esto evita repetir el texto en cinco lugares distintos (caso `'?'`, caso `-h`, caso falta de argumentos, etc.)
y garantiza que **siempre** se muestra el mismo texto.
```c
#include <stdio.h>
/* Declaramos la funcion antes de main: recibe el nombre del programa y muestra la ayuda por stdout. */
static void print_help(const char *progname)
{
printf("Uso: %s [opciones] <archivo_origen> <archivo_destino>\n", progname);
printf("Argumentos posicionales:\n");
printf(" archivo_origen Ruta del archivo que se va a leer (obligatorio, posicion 1).\n");
printf(" archivo_destino Ruta del archivo donde se guardara el resultado (obligatorio, posicion 2).\n");
printf("\n");
printf("Opciones:\n");
printf(" -h, --help Muestra esta ayuda y termina.\n");
printf(" -v, --verbose Activa el modo detallado.\n");
printf(" -o, --output <archivo> Guarda el resultado en el archivo indicado.\n");
printf(" -m, --max <N> Numero maximo de elementos a procesar (por defecto: 100).\n");
}
```
Despues, la llamás desde cualquier lugar donde necesites mostrar ayuda:
```c
case 'h':
print_help(argv[0]);
return 0;
case '?':
fprintf(stderr, "Error: opcion invalida.\n\n");
print_help(argv[0]);
return 1;
```
> **Momento ajá.** La ventaja de `print_help` no es solo que escribís menos código: es que si mañana
> agregás una opción nueva, la modificás en un solo lugar (`print_help`) y **todas** las salidas se
> actualizan automáticamente. Sin ella, tendrías que buscar y reemplazar cada aparición del texto de
> ayuda —y muy probablemente te olvidás de una.
## 7.5.4 stdout vs stderr para la ayuda
La ayuda tiene un comportamiento mixto:
- Cuando el usuario **pide** la ayuda explícitamente (`-h` o `--help`): va por **`stdout`**. El usuario
pidió ver algo; que salga en pantalla sin interferir con redirecciones es lo correcto.
- Cuando la ayuda aparece como **consecuencia de un error** (opción desconocida, falta de argumentos):
va por **`stderr`**, porque está acompañando un mensaje de error.
Esto permite dos flujos distintos:
```bash
./mi_programa --help > ayuda.txt # el usuario guardó la ayuda en un archivo
./mi_programa --desconocida # el error con ayuda aparece en pantalla, no se pierde
```
## Práctica de la Parte 7.5
### Ejercicio 13.12 -- Crear la funcion print_help
**Conceptos** · `printf`, estructura de ayuda, separación stdout/stderr
**Contexto** · Dado un conjunto de opciones y posicionales, escribir la función `print_help` que los
muestre con el formato estándar del capítulo. Simplificacion: no escribís el programa principal, solo
la funcion de ayuda.
**Consigna** · Escribí una función `print_help(const char *progname)` para un programa que tiene:
| Corta | Larga | Valor? | Descripcion |
|---|---|---|---|
| `-h` | `--help` | no | Muestra esta ayuda y termina. |
| `-q` | `--quiet` | no | Suprime los mensajes informativos. |
| `-d` | `--database` | si | Especifica la base de datos a conectar. |
Y que espera dos argumentos posicionales:
1. `archivo_conf` — Ruta del archivo de configuración (obligatorio, posición 1).
2. `archivo_log` — Ruta del archivo de registro de eventos (opcional, posición 2, valor por defecto: `/dev/null`).
El formato debe seguir **exactamente** la plantilla de la sección 7.5.2, con las secciones en este orden:
Uso → Argumentos posicionales → Opciones.
**Entrada** · Los datos arriba.
**Salida** · La función C completa `print_help`.
**Identificadores sugeridos** · `progname`
**Criterios de aceptación**
- [ ] La línea de Uso usa `progname` y la notación correcta (`<>`, `[]`).
- [ ] Los posicionales se listan con sus descripciones completas, incluyendo obligatorios/opcionales y posiciones.
- [ ] Las opciones muestran ambas variantes (corta/larga) separadas por coma.
- [ ] Las opciones con valor incluyen `<nombre>` entre la opción y la descripción.
- [ ] Hay una línea en blanco entre la sección de posicionales y la de opciones.
### Ejercicio 13.13 -- Usar print_help en el programa completo
**Conceptos** · `getopt_long`, `print_help`, `stdout`/`stderr`, flujo de ayuda
**Contexto** · Integrar `print_help` en un programa real con `getopt_long`. Simplificacion: el
programa solo muestra la ayuda y termina; no procesa ningun dato.
**Consigna** · Escribí un programa completo que:
1. Use `getopt_long` con las opciones `-h`, `-q`, `-d <database>`.
2. Llame a `print_help(argv[0])` (definida por vos siguiendo el formato estándar) cuando se detecta `-h`/`--help`, retornando 0.
3. Muestre `print_help(argv[0])` por `stderr` y retorne 1 cuando `getopt_long` devuelve `'?'`.
4. Si no recibe el argumento posicional obligatorio (`archivo_conf`), muestre la ayuda por `stderr` con un mensaje de error antes y retorne 1.
5. Si todo está bien, imprima: `Conectando a base de datos: <valor_de_d> desde <archivo_conf>` por `stdout` y retorne 0.
**Escenario de prueba**
```text
$ ./conector --help
Uso: ./conector [opciones] <archivo_conf> [archivo_log]
Argumentos posicionales:
archivo_conf Ruta del archivo de configuración (obligatorio, posición 1).
archivo_log Ruta del archivo de registro (opcional, posición 2, por defecto: /dev/null).
Opciones:
-h, --help Muestra esta ayuda y termina.
-q, --quiet Suprime los mensajes informativos.
-d, --database <database> Especifica la base de datos a conectar.
```
```text
$ ./conector
Error: se requiere un archivo de configuración
Uso: ./conector [opciones] <archivo_conf> [archivo_log]
... (resto de la ayuda por stderr)
```
```text
$ ./conector -d mi_db config.ini
Conectando a base de datos: mi_db desde config.ini
```
**Criterios de aceptación**
- [ ] `print_help` existe como funcion estatica y se llama desde `-h`, `'?'`, y falta de argumentos.
- [ ] La línea de Uso coincide exactamente con el formato canónico.
- [ ] El caso `'?'` imprime un error por `stderr` ANTES de mostrar la ayuda (tambien por `stderr`).
- [ ] `-h` muestra todo por `stdout` y retorna 0.
- [ ] Falta de posicional obligatorio muestra error + ayuda por `stderr`, retorna 1.
- [ ] Flujo exitoso imprime el mensaje correcto por `stdout`, retorna 0.
---
# PARTE 8 -- El ejercicio jefe
## 8.1 La prueba final
Llegó el momento de juntar todo lo aprendido en un solo programa. Este no es un ejercicio cualquiera:
es una **herramienta de consola completa** que usa `getopt_long` para interpretar opciones cortas,
opciones largas con valores, y deja los posicionales intactos al final.
### Ejercicio 13.14 -- Utilidad de consola completa (JEFE)
**Conceptos** · `getopt_long`, `struct option`, `optarg`, `optind`, `stderr`, códigos de salida,
variables locales, bucle posicional, `print_help`.
**Contexto** · Reunir **todo** el capítulo en una sola herramienta real de línea de comandos. Es una
**simplificación**: no procesa archivos de verdad, pero su interfaz es la de una utilidad profesional.
**Consigna** · Desarrollá un programa que interprete estas opciones usando `getopt_long`:
| Corta | Larga | ¿Valor? | Acción |
|---|---|---|---|
| `-h` | `--help` | no | Muestra la ayuda y termina con 0. |
| `-v` | `--verbose` | no | Activa el modo detallado. |
| `-o` | `--output` | sí | Guarda el archivo de salida indicado. |
| `-m` | `--max` | sí | Guarda el máximo (número entero). |
Y que además:
1. Defina una funcion `static void print_help(const char *progname)` siguiendo el formato estándar de la Parte 7.5.
2. Use `print_help(argv[0])` tanto para `-h`/`--help` (por `stdout`, retorno 0) como para errores por `'?'` (por `stderr`, retorno 1).
3. Acepte las **dos formas** de valor para las largas (`--output=x` y `--output x`). Esto lo resuelve
`getopt_long` automáticamente --no necesitás hacer nada extra.
4. Trate los argumentos **no-opción** como archivos posicionales (listados al final usando `optind`).
5. Si no recibe ningún posicional **ni** `--output`, avise que no hay nada que hacer (error claro).
**Entrada** · Argumentos por línea de comandos.
**Salida** · El efecto de las opciones por `stdout`; los errores por `stderr`.
**Escenario de prueba**
```text
$ ./util --verbose --max=10 entrada.txt salida.txt
Modo verbose: ON
Maximo: 10
Archivos a procesar: 2
Archivo 1: entrada.txt
Archivo 2: salida.txt
```
```text
$ ./util -v --max=5 datos.csv
Modo verbose: ON
Maximo: 5
Archivos a procesar: 0
Aviso: no hay archivos de entrada ni archivo de salida definido
```
```text
$ ./util --help
Uso: ./util [opciones] <archivos>...
Argumentos posicionales:
archivos Lista de archivos a procesar (al menos uno obligatorio).
Opciones:
-h, --help Muestra esta ayuda y termina.
-v, --verbose Activa el modo detallado.
-o, --output <archivo> Archivo de salida.
-m, --max <N> Maximo de elementos (por defecto: 100).
```
**Identificadores sugeridos** · `opt`, `long_options`, `option_index`, `optind`, `verbose`,
`max_value`, `output_name`, `i`
**Restricciones / pistas**
- Usá `getopt_long` con el `optstring` `"hvo:m:"`.
- **No** olvides el terminador `{0,0,0,0}` de la tabla.
- El `-h` tiene **prioridad**: mostralo apenas aparece y terminá.
- Los posicionales salen de `argv[optind..argc-1]`.
- Errores → `stderr`; resultados → `stdout`.
- Si no hay archivos ni output definido, mostrá un aviso (no es error fatal, pero sí informativo).
**Criterios de aceptación**
- [ ] Reconoce `-v`/`--verbose`, `-o`/`--output` y `-m`/`--max`.
- [ ] Acepta valor pegado (`--max=10`) y separado (`--max 10`).
- [ ] `print_help` existe como funcion estatica con el formato canónico.
- [ ] `-h`/`--help` muestra ayuda por `stdout` y retorna 0.
- [ ] Una opción desconocida produce error por `stderr` (con help incluido) y código != 0.
- [ ] Los posicionales se listan después de las opciones (usando `optind`).
- [ ] El formato coincide con los escenarios de prueba.
- [ ] Sin archivos ni output, muestra un aviso informativo.
---
## Cierre: lo que te llevás
Si tuvieras que quedarte con **una sola idea**, que sea esta: `getopt_long` hace todo el trabajo pesado
de parsear opciones para vos --vos solo declarás qué opciones reconocés y qué querés hacer con cada una.
- **La llamada es siempre la misma.** `getopt_long(argc, argv, "hvo:m:", long_options, &option_index)`.
Cambiá el optstring, cambiá la tabla, pero el patrón no cambia.
- **`struct option` tiene cuatro campos.** Nombre sin guiones, macro de argumento, `0`, código corto.
Y el terminador `{0,0,0,0}` al final --siempre.
- **Tres macros definen si lleva valor.** `no_argument` (flag), `required_argument` (lleva siempre un
valor), `optional_argument` (puede llevar o no).
- **Tres variables globales hacen el trabajo sucio.** `optarg` tiene el valor de la opción, `optind`
apunta al primer posicional, `optopt` tiene la letra de la opción desconocida.
- **`optind` es tu puente hacia los datos reales.** Después del bucle `while`, un `for` desde
`optind` hasta `argc` te da todos los posicionales --archivos, nombres, lo que importa.
- **El caso `'?'` captura todos los errores.** Si la opción es desconocida o falta un valor, `getopt_long`
devuelve `'?'`. Manejalos mostrando ayuda por `stderr` y retornando 1.
- **La ayuda se imprime con un helper propio.** No existe funcion estandar en C para generarla automaticamente;
el patrón es crear una `print_help(const char *progname)` que centralice el texto y lo muestre siguiendo
el formato canónico (Uso → posicionales → opciones).
Y sobre todo: aprendiste a **delegar**. Escribir un parser de opciones desde cero es posible (y lo hiciste
en el capítulo anterior), pero `getopt_long` lo hace mejor, más consistente, y con menos líneas de código.
Eso no es trampa --es programar inteligente.