diff --git a/book/13-argumentos-de-linea-de-comandos.md b/book/13-argumentos-de-linea-de-comandos.md deleted file mode 100644 index b8f108a..0000000 --- a/book/13-argumentos-de-linea-de-comandos.md +++ /dev/null @@ -1,1444 +0,0 @@ -# Capítulo 13 — Argumentos de línea de comandos: de cero a pro - -> Capítulo intermedio, pero **escrito para que se entienda de verdad**. Se apoya en variables (`02`), -> operadores (`03`), `if` y bucles (`04`), funciones (`05`), arreglos y cadenas (`06`) y punteros (`07`). -> -> Es la **antesala** del capítulo de entrada/salida estándar (`14-entrada-salida-estandar`). Acá -> aprendés a que tu programa reciba **órdenes al nacer** (cuando lo ejecutás) en lugar de pedirlas -> recién mientras corre. En el capítulo siguiente vas a profundizar en las tres tuberías -> (`stdin`/`stdout`/`stderr`), que acá tocamos apenas lo justo. - ---- - -## Cómo leer este capítulo - -Hasta ahora, todos tus programas empezaban **preguntando**. Le mostraban un cartel al usuario y -esperaban a que escribiera algo por teclado. Eso funciona, pero no es la única forma. Las herramientas -serias —`ls`, `grep`, `git`, `gcc`— no te preguntan nada: **les pasás las órdenes en el mismo momento -en que las ejecutás**. - -Ese es el tema de este capítulo. - -Está dividido en **nueve partes**. Después de cada idea importante hay una **práctica** —uno o varios -ejercicios cortos— para que la uses ya mismo, antes de avanzar. **No saltees las prácticas**: son la -mitad del capítulo. - -| Parte | De qué habla | La idea en una frase | -|---|---|---| -| **1** | Órdenes al nacer | Tu programa puede recibir los datos cuando lo ejecutás, no después. | -| **2** | `argc` y `argv` | El par mágico que `main` recibe con la cantidad y la lista de argumentos. | -| **3** | `argv[0]` | El primer argumento siempre es **cómo llamaron** a tu programa. | -| **4** | Recorrer y clasificar | Cómo leer los argumentos uno por uno y distinguir qué tipo es cada uno. | -| **5** | Opciones cortas y largas | `-v` vs `--verbose`: dos estilos que conviven en toda herramienta. | -| **6** | Opciones con valor | `--dato=2` o `--dato 2`: cómo leer el valor que acompaña a una opción. | -| **7** | Validar y avisar | Errores por `stderr`, códigos de salida y un `--help` digno. | -| **8** | `getopt` | El parseo ordenado de opciones cortas, con soporte para `-abc` apiladas. | -| **9** | `getopt_long` | El nivel pro: opciones largas al estilo `--output=archivo`. | - -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 — Tu programa puede recibir órdenes al nacer - -Antes de tocar una sola línea de código, entendamos **qué cambia** y **por qué** esto es tan útil. - -## 1.1 La diferencia entre preguntar y que te digan - -Pensá en una **máquina expendedora**. - -- **Forma antigua (preguntar durante la ejecución):** prendés la máquina y ella te va preguntando: - *"¿Qué querés?"*. Vos respondés por un teclado mientras la máquina ya está funcionando. En C, eso es - usar `scanf`, `getchar` o `fgets` sobre `stdin`: el programa **se frena y espera**. -- **Forma nueva (recibir órdenes al nacer):** **le das los ingredientes ANTES de encenderla**. Le decís: - *"2 latas de Coca-Cola"*. La ejecutás y ya sabe qué hacer: **no pregunta, va al resultado**. - -Eso es exactamente lo que pasa cuando escribís en la terminal: - -```bash -./programa test.txt --alguna-opcion --dato=2 -``` - -Le estás pasando **órdenes al programa en el mismo momento en que lo ejecutás**. Son los **argumentos -de línea de comandos** (en inglés, *command-line arguments*). - -> **Momento ajá.** La diferencia clave es el **momento**: los argumentos llegan **al arrancar el -> programa** (cuando empieza `main`), no durante su ejecución. - -## 1.2 ¿Por qué esto importa? - -Porque es **el** mecanismo con el que se automatiza trabajo en la vida real. - -- Un script de respaldo hace `./backup --origen=/data --destino=/backup --comprimir`. -- Un analizador de logs hace `./analiza server.log --desde=2026-01-01 --nivel=error`. -- El compilador hace `gcc -o salida programa.c -Wall`. - -Nadie está sentado respondiendo preguntas: los datos **viajan con la orden**. Eso permite que un -programa llame a otro, encadenarlos con tuberías y automatizarlos con tareas programadas. - -## 1.3 Lo que ya conocés, y lo que viene - -Ya sabés que `main` es la puerta de entrada del programa. Su forma mínima es: - -```c -int main(void) -{ - /* ... */ - return 0; -} -``` - -Ese `int` de adelante es el **código de retorno**: `0` significa "salió bien". Ya lo veníamos usando. - -Para recibir argumentos, `main` admite **dos parámetros extra**. Esa es toda la novedad: - -```c -int main(int argc, char *argv[]) -{ - /* ... */ - return 0; -} -``` - -No pasa nada raro: es la misma función `main`, con dos datos de entrada más. En la Parte 2 los -desarmamos pieza por pieza. - -## Práctica de la Parte 1 - -### Ejercicio 13.1 — Pensar antes de ejecutar - -**Conceptos** · argumentos de línea de comandos, diferencia con la lectura por teclado - -**Contexto** · Antes de escribir código, hay que entender **cuándo** llegan los datos. Simplificación: -dos formas de pedirle algo a un programa. - -**Consigna** · Leé estas dos escenas y respondé por escrito (sin programar): - -1. Ejecutás `./programa`, aparece el cartel `¿Archivo?` y vos escribís `datos.txt`. -2. Ejecutás `./programa datos.txt` directamente desde la terminal. - -¿En cuál de las dos el programa **ya sabe qué archivo usar desde el arranque**? ¿Cuál corresponde a -"argumentos de línea de comandos"? - -**Entrada** · Ninguna (es un ejercicio de análisis). -**Salida** · Una respuesta escrita comparando ambas formas. - -**Escenario de prueba** · No aplica (no hay programa). - -**Criterios de aceptación** -- [ ] Identificás que en la escena 2 los datos llegan **al ejecutar**. -- [ ] Explicás la diferencia entre pedir datos **durante** la ejecución y **al ejecutarla**. -- [ ] Nombrás correctamente la forma 2 como "argumentos de línea de comandos". - ---- - -# PARTE 2 — `argc` y `argv`: el par mágico - -## 2.1 `argc`: el contador - -Cuando ejecutás un programa desde la consola, el **sistema operativo** (a través del shell) le entrega -a `main` **cuántas** palabras escribiste y **cuáles** fueron. - -El primero de los dos parámetros es `argc`: - -- Significa **"argument count"**: *cantidad de argumentos*. -- Es un `int`. -- Cuenta **cuántas palabras** separadas por espacios llegaron en la línea de comandos. - -**Analogía.** Imaginá una lista de compras escrita en un papel. `argc` es **el número de ítems** del -papel. No te dice qué comprar: solo **cuántas cosas** hay. - -> **Para guardar.** `argc` **nunca es 0** en un programa ejecutado normalmente. Siempre cuenta al -> menos **1**. ¿Por qué? Lo vas a ver en la Parte 3 (spoiler: el nombre del programa también cuenta). - -## 2.2 `argv`: la lista - -El segundo parámetro es `argv`: - -- Significa **"argument vector"**: *vector (lista) de argumentos*. -- Es un **arreglo de cadenas**: `char *argv[]`. -- Cada posición guarda **una palabra** exactamente como apareció en la terminal. - -**Analogía.** Si `argc` es el número de ítems de la lista, `argv` es **la lista misma**: un estante -donde cada balda guarda una palabra completa. `argv[0]` es el primer estante, `argv[1]` el segundo, y -así hasta `argv[argc-1]`. - -> **Un detalle jurídico.** `argv` es un arreglo de **punteros a `char`** (`char *`). Cada elemento -> apunta al comienzo de una cadena terminada en `'\0'`. Por eso se escribe `char *argv[]` y no -> `char argv[]`: no guardamos las letras, guardamos **la dirección** de dónde empiezan. Si todavía -> tenés dudas con punteros, repasá el capítulo `07-punteros`. - -## 2.3 El ejemplo completo, palabra por palabra - -Si ejecutás: - -```bash -./programa test.txt --alguna-opcion --dato=2 -``` - -el sistema reparte los argumentos así: - -| Índice | Valor de `argv[i]` | ¿Qué es? | -|---|---|---| -| `0` | `"./programa"` | El nombre con el que se ejecutó. | -| `1` | `"test.txt"` | Un argumento pasado. | -| `2` | `"--alguna-opcion"` | Otro argumento. | -| `3` | `"--dato=2"` | Otro más. | - -Y `argc` vale **4** (hay 4 palabras). Fijate que `argc` **cuenta también el nombre**: por eso -`argv[0]` a `argv[3]` son 4 posiciones, y `argc = 4`. - -> **Ojo con esto.** El **espacio** es lo que separa un argumento del siguiente. Cada bloque entre -> espacios se convierte en un `argv[i]` distinto. Si querés que un texto con espacios sea **un solo** -> argumento, hay que **encerrarlo entre comillas**: `./programa "hola mundo"` llega como un único -> `argv[1]` igual a `"hola mundo"`. - -## 2.4 Diagrama mental - -```text - Línea: ./programa test.txt --alguna-opcion --dato=2 - │ │ │ │ - ▼ ▼ ▼ ▼ - argv ─► ┌────────┐┌─────────┐┌──────────────┐┌─────────┐ - │[0] ││[1] ││[2] ││[3] │ ... - │"./prog"││"test.txt"││"--alguna-op" ││"--dato=2"│ - └────────┘└─────────┘└──────────────┘└─────────┘ - argc = 4 (cuatro palabras, incluido el nombre) -``` - -## 2.5 El caso más chico posible - -Si ejecutás: - -```bash -./saludo -``` - -obtenés: - -- `argc = 1` -- `argv[0] = "./saludo"` - -Aunque **no pasaste nada extra**, `argc` vale 1: el nombre del programa **siempre** ocupa el estante 0. - -## Práctica de la Parte 2 - -### Ejercicio 13.2 — Contar y adivinar - -**Conceptos** · `argc`, `argv`, índice de argumentos - -**Contexto** · Antes de programar, hay que **leer** una línea de comandos como la leería el sistema. -Simplificación: adivinar valores sin ejecutar nada. - -**Consigna** · Dada la invocación: - -```bash -./programa archivo.txt -v 42 -``` - -respondé sin programar: - -1. ¿Cuánto vale `argc`? -2. ¿Qué contiene `argv[0]`, `argv[1]`, `argv[2]` y `argv[3]`? -3. ¿Por qué `argc` **no** vale 3? - -**Entrada** · La línea de comandos del enunciado. -**Salida** · Las tres respuestas. - -**Escenario de prueba** · No aplica (no hay programa). - -**Criterios de aceptación** -- [ ] `argc` correcto (recordá que el nombre cuenta). -- [ ] Los cuatro valores de `argv` correctos y en orden. -- [ ] Justificás el punto 3 mencionando que `argv[0]` es el nombre del programa. - -### Ejercicio 13.3 — Eco de argumentos - -**Conceptos** · `argc`, `argv`, bucle `for`, `%s` - -**Contexto** · El primer programa "de verdad" con argumentos: repetir lo que recibió. Simplificación: -un eco que no interpreta nada, solo muestra. - -**Consigna** · Escribí un programa que, al ejecutarlo, muestre por pantalla **la cantidad** de -argumentos y luego **cada argumento** en una línea, en el formato del escenario de prueba. Recorré el -arreglo con un bucle `for`. - -**Entrada** · Los argumentos que le pases al ejecutarlo. -**Salida** · Una línea con la cantidad y una línea por argumento, con el formato exacto del ejemplo. - -**Escenario de prueba** - -```text -$ ./eco hola mundo 123 -argc = 4 -argv[0] = ./eco -argv[1] = hola -argv[2] = mundo -argv[3] = 123 -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i` - -**Restricciones / pistas** -- El bucle debe llegar **hasta `argc-1`**: nunca leas `argv[argc]` (estarías fuera del arreglo). -- Empezás en `i = 0` si querés mostrar **todos** (incluido el nombre). En el próximo ejercicio - empezarás en `i = 1`. - -**Criterios de aceptación** -- [ ] Muestra el valor correcto de `argc`. -- [ ] Recorre de `0` a `argc-1` con un `for`. -- [ ] El formato coincide exactamente con el escenario de prueba. -- [ ] No accede a ninguna posición fuera del rango válido. - ---- - -# PARTE 3 — `argv[0]`: el nombre del programa - -## 3.1 El argumento número cero es especial - -`argv[0]` guarda **cómo se llamó al programa** en esa ejecución concreta. No es un dato que pasaste -vos: es el nombre con el que el sistema lo invocó. - -**Analogía.** Es como presentarte: *"Hola, soy Horacio"*. `argv[0]` es **el nombre con el que te -llamaron** en ese momento, no quién sos en realidad. - -Puede ser una ruta relativa (`./programa`), absoluta (`/usr/bin/programa`) o, en Windows, incluir -`.exe` (`programa.exe`). Todo depende de **cómo lo escribiste al ejecutarlo**. - -## 3.2 ¿Para qué sirve saber el nombre? - -Para **mensajes honestos**. - -Cuando un programa necesita decirle al usuario cómo usarlo (un mensaje de "Uso: ..."), lo correcto es -mostrar **el mismo nombre con el que lo llamaron**, no uno fijo escrito a mano. - -- Si el usuario lo ejecutó como `./mini`, tu ayuda debería decir `Uso: ./mini ...`. -- Si lo ejecutó como `mini`, debería decir `Uso: mini ...`. - -**Nunca hardcodees el nombre del ejecutable.** Usá `argv[0]`. - -> **Momento ajá.** El truco `printf("Uso: %s \n", argv[0]);` es uno de los patrones más -> repetidos de la programación de consola. Guardátelo. - -## 3.3 Un caso de borde - -`argv[0]` **puede estar vacío** (`""`) en situaciones muy raras de algunos sistemas, pero en la -práctica casi nunca ocurre. Lo normal es que tenga el nombre, relativo o absoluto. - -## Práctica de la Parte 3 - -### Ejercicio 13.4 — El nombre con el que me llamaron - -**Conceptos** · `argv[0]`, cadenas, `printf` con `%s` - -**Contexto** · Todo programa de consola debería mostrar su propio nombre en las ayudas. Simplificación: -mostrar el nombre y una línea de uso genérica. - -**Consigna** · Escribí un programa que muestre su nombre (`argv[0]`) y un mensaje de uso que lo -incluya **dinámicamente**, con el formato del escenario de prueba. - -**Entrada** · Cualquier cosa (el programa no necesita argumentos extra). -**Salida** · Dos líneas con el formato exacto del ejemplo. - -**Escenario de prueba** - -```text -$ ./demo archivo.txt -Programa: ./demo -Uso: ./demo [opciones] -``` - -**Identificadores sugeridos** · `argv` - -**Restricciones / pistas** -- El nombre del programa **no** debe escribirse literal: usá `argv[0]`. -- Si lo ejecutás con otra ruta (`./carpeta/demo`), el mensaje debe reflejarlo. - -**Criterios de aceptación** -- [ ] Muestra `argv[0]` tal cual. -- [ ] El mensaje de uso usa `argv[0]`, no un texto fijo. -- [ ] Cambia correctamente si el programa se ejecuta con otra ruta. - ---- - -# PARTE 4 — Recorrer y clasificar argumentos - -## 4.1 El patrón para recorrer (sin salirse nunca) - -Para leer los argumentos que **le pasaste al programa** (no el nombre), se recorre desde `argv[1]` -**hasta `argv[argc-1]`**. La condición del bucle es **siempre** `i < argc`. - -```c -#include - -int main(int argc, char *argv[]) -{ - /* Recorro desde 1: el 0 (argv[0]) es el nombre del programa. */ - for (int i = 1; i < argc; i++) { - printf("Argumento %d: %s\n", i, argv[i]); - } - return 0; -} -``` - -- **Empezamos en 1** porque `argv[0]` es el nombre, no un dato de tu tarea. -- **Terminamos cuando `i == argc`** porque la última posición válida es `argc-1`. - -> **Trampa N.º 1 — salirse del arreglo.** Si la condición fuera `i <= argc`, en algún momento leerías -> `argv[argc]`, que **no existe**: es memoria basura. El resultado es un *segfault* o datos -> impredecibles. La condición correcta es **`i < argc`, sin excepción**. - -## 4.2 Ahora sí: no todos los argumentos son iguales - -En `./programa test.txt --alguna-opcion --dato=2` hay **tres clases** de argumentos. Distinguirlos es -el corazón del parseo. - -| Clase | Ejemplo | Pista para reconocerlo | -|---|---|---| -| **Posicional** | `test.txt` | **No** empieza con `-`. Su **posición** importa (es el "primer dato", el "segundo dato"...). | -| **Opción (flag)** | `--alguna-opcion` | Empieza con `-` o `--` y **no** lleva valor. Activa/desactiva algo. | -| **Opción con valor** | `--dato=2` | Es una opción **y** trae su valor (con `=` o en el argumento siguiente). | - -**Analogía de pedido por delivery.** - -- `pedido.txt` → **qué** hay que procesar (posicional). -- `--rapido` → **una opción**: "querés rápido" (sí/no). -- `--cantidad=2` → **opción con valor**: "querés 2". - -## 4.3 La regla del guión - -La pista principal para reconocer una opción es simple: **empieza con `-`**. - -```text -"-" ... no es opción (guión solo: suele significar "leer de stdin") -"-x" ... opción corta -"--" ... no es una opción (separador especial "fin de opciones") -"--x" ... opción larga -"x" ... argumento posicional -``` - -Cuidado con los casos de la tabla: un `"-"` solito **no** es una opción, y un `"--"` solito tampoco -(suele marcar *"de acá en adelante, todo es posicional"*). A esos casos raros les prestamos atención -en la Parte 5. - -> **Trampa N.º 2 — confundir posición con opción.** Los **posicionales** dependen del **orden**: el -> primer nombre de archivo es el de entrada, el segundo el de salida. Las **opciones** suelen poder ir -> en cualquier orden. No mezcles ambas ideas. - -## 4.4 Un esqueleto de clasificación (patrón, no solución) - -Para clasificar a mano, un patrón típico es mirar la forma del argumento: - -```c -for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1] == '-') - /* opción larga */; - else if (argv[i][0] == '-') - /* opción corta */; - else - /* posicional */; -} -``` - -Fijate los índices: `argv[i][0]` es el **primer carácter** de la palabra; `argv[i][1]` el **segundo**. -Por eso `argv[i][0] == '-'` pregunta "¿empieza con guión?" y `argv[i][1] == '-'` pregunta "¿y el -siguiente también?" (o sea, `--`). - -> **Ojo con esto.** Este es un **patrón ilustrativo** para entender cómo se mira una cadena por dentro. -> Cuidado con los **casos de borde** (`"-"` y `"--"`): el patrón de arriba los trataría como opciones. -> Un clasificador robusto los contempla aparte. - -## Práctica de la Parte 4 - -### Ejercicio 13.5 — Listar solo los parámetros útiles - -**Conceptos** · `argc`, `argv`, bucle, índices - -**Contexto** · Muchas herramientas listan lo que recibieron, sin el nombre del programa. -Simplificación: un listado de los argumentos pasados. - -**Consigna** · Escribí un programa que muestre **únicamente** los argumentos posteriores al nombre -(desde `argv[1]`), uno por línea, con el formato del escenario de prueba. - -**Entrada** · Los argumentos pasados al ejecutarlo. -**Salida** · El listado pedido, sin incluir `argv[0]`. - -**Escenario de prueba** - -```text -$ ./filtrar -r --debug datos.csv -Parámetros recibidos: -- -r -- --debug -- datos.csv -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i` - -**Restricciones / pistas** -- Recorré desde `i = 1`. -- No imprimas `argv[0]`. - -**Criterios de aceptación** -- [ ] No muestra el nombre del programa. -- [ ] Recorre de `1` a `argc-1`. -- [ ] Formato idéntico al ejemplo. - -### Ejercicio 13.6 — Contador de parámetros extra - -**Conceptos** · `argc`, aritmética simple - -**Contexto** · Los reportes suelen resumir cuántos datos recibieron. Simplificación: contar los -argumentos extra. - -**Consigna** · Escribí un programa que informe **cuántos argumentos extra** (sin contar el nombre) -recibió, con el formato del escenario de prueba. - -**Entrada** · Los argumentos pasados al ejecutarlo. -**Salida** · Una línea con la cantidad. - -**Escenario de prueba** - -```text -$ ./contar a b c d -Recibiste 4 parámetro(s) extra(s). -``` - -```text -$ ./contar -Recibiste 0 parámetro(s) extra(s). -``` - -**Identificadores sugeridos** · `argc`, `extraCount` - -**Restricciones / pistas** -- La cantidad de extra es `argc - 1`. Los corchetes del ejemplo son literales: no hace falta manejar - plural/singular finamente. - -**Criterios de aceptación** -- [ ] Con `a b c d` informa `4`. -- [ ] Sin argumentos informa `0`. -- [ ] El cálculo usa `argc` (no cuenta a mano). - -### Ejercicio 13.7 — Clasificador de argumentos - -**Conceptos** · `argv[i][0]`, comparación de cadenas, clasificación - -**Contexto** · Antes de interpretar opciones, hay que saber qué tipo de argumento es cada uno. -Simplificación: clasificar sin ejecutar ninguna acción. - -**Consigna** · Escribí un programa que recorra desde `argv[1]` y clasifique cada argumento como: - -- `[OPCION LARGA]` si empieza con `--` y tiene algo después; -- `[OPCION CORTA]` si empieza con **un** `-` y tiene algo después; -- `[POSICIONAL]` si no empieza con `-`. - -**Entrada** · Los argumentos pasados al ejecutarlo. -**Salida** · Una línea por argumento con el formato del ejemplo. - -**Escenario de prueba** - -```text -$ ./clasifica -v --verbose archivo.txt -h --v -> [OPCION CORTA] ---verbose -> [OPCION LARGA] -archivo.txt -> [POSICIONAL] --h -> [OPCION CORTA] -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i` - -**Restricciones / pistas** -- Distinguí `--` de `-` mirando el **segundo** carácter. -- Contemplá (aunque sea mínimamente) los casos `"-"` y `"--"` para no clasificarlos mal. - -**Criterios de aceptación** -- [ ] Distingue correctamente los tres tipos. -- [ ] No confunde `--` con `-`. -- [ ] Recorre solo los argumentos extra. -- [ ] El formato coincide con el ejemplo. - ---- - -# PARTE 5 — Opciones cortas y largas (`-v` vs `--verbose`) - -## 5.1 Dos estilos, una misma idea - -Las herramientas de consola usan **dos estilos** para las opciones. No compiten: **conviven**. - -| Estilo | Prefijo | Ejemplos | Para qué sirve | -|---|---|---|---| -| **Corta** | `-` (un guión) | `-v`, `-h`, `-r`, `-o` | Rápida de tipear. Casi siempre letra única. | -| **Larga** | `--` (dos guiones) | `--verbose`, `--help`, `--output` | Se lee como palabra. Ideal para no memorizar letras. | - -**Analogía.** Es como abreviaturas vs. palabras completas: `tel.` vs. `teléfono`. Las dos sirven; la -completa deja menos dudas. - -La buena noticia: casi siempre `-v` y `--verbose` **hacen lo mismo**. Es el mismo botón con dos -etiquetas. Tu trabajo es que las dos etiquetas activen el mismo comportamiento. - -## 5.2 Opciones cortas apiladas: `-abc` - -Un detalle muy cómodo de las opciones **cortas**: se pueden **apilar**. Escribir: - -```bash -./programa -abc -``` - -es lo mismo que: - -```bash -./programa -a -b -c -``` - -Son **tres** opciones cortas juntas. Esto **solo** aplica a las cortas (`-`); las largas (`--`) nunca -se apilan así. - -> **Ojo con esto.** "Apilar" no es gratis: si querés soportarlo **a mano**, tenés que recorrer cada -> carácter del argumento. Por suerte, en la Parte 8 veremos que `getopt` **lo hace por vos**. - -## 5.3 El separador `--` (fin de opciones) - -Existe una convención poderosa: un argumento que es **solo** `--` le dice al programa -*"de acá en adelante, todo es posicional, aunque empiece con guión"*. Se usa para archivos con nombres -raros: - -```bash -./programa -- --archivo-que-empieza-con-guiones.txt -``` - -Acá `--archivo-...` es un **archivo**, no una opción. Es un caso avanzado; mencionarlo te evita -sorpresas cuando leas el código de otras herramientas. - -## 5.4 Detectar una opción con seguridad - -Un chequeo básico y seguro (contemplando el caso `"-"`): - -```c -if (argv[i][0] == '-' && argv[i][1] != '\0') { - /* Es una opción: arranca con '-' y no es solo "-" */ -} -``` - -Ese `!= '\0'` pregunta "¿hay algo **después** del guión?". Así un `"-"` solito (que suele significar -*"leer de la entrada estándar"*) **no** se confunde con una opción. - -## Práctica de la Parte 5 - -### Ejercicio 13.8 — Simulador de ayuda - -**Conceptos** · comparación de cadenas (`strcmp`), opciones cortas y largas, terminación del programa - -**Contexto** · Toda herramienta debe poder explicarse sola. `-h` y `--help` son el estándar de facto. -Simplificación: mostrar una ayuda fija y salir. - -**Consigna** · Escribí un programa que, si recibe **`-h` o `--help`** (cualquiera de las dos), muestre -un mensaje de ayuda y **termine**. Si no las recibe, no debe mostrar la ayuda. - -**Entrada** · Argumentos por línea de comandos. -**Salida** · El mensaje de ayuda (con el nombre del programa incluido), solo si corresponde. - -**Escenario de prueba** - -```text -$ ./ayuda -h -Uso: ./ayuda [opciones] -Opciones: - -h, --help Muestra esta ayuda - -v, --verbose Modo detallado -``` - -```text -$ ./ayuda --help -Uso: ./ayuda [opciones] -Opciones: - -h, --help Muestra esta ayuda - -v, --verbose Modo detallado -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i` - -**Restricciones / pistas** -- Tenés que reconocer **las dos** variantes: `-h` **y** `--help`. -- Para comparar cadenas completas usá `strcmp` (no `==`, que compara direcciones). -- Al mostrar ayuda, el programa debe **terminar**: no sigas procesando. -- La ayuda usa `argv[0]` en la línea `Uso: ...`. - -**Criterios de aceptación** -- [ ] Detecta tanto `-h` como `--help`. -- [ ] Son variantes equivalentes (mismo resultado). -- [ ] Tras mostrar la ayuda, el programa **termina**. -- [ ] La línea `Uso:` usa `argv[0]`. - ---- - -# PARTE 6 — Opciones con valor - -## 6.1 El problema - -Muchas opciones necesitan **un valor**: `--max=10`, `--output=resultado.txt`, `-o salida.txt`. El -valor es el dato que la opción lleva consigo. - -Hay **dos formas** de pasarlo, y las dos son válidas: - -| Forma | Ejemplo | En `argv` queda... | -|---|---|---| -| **Pegada con `=`** | `--dato=2` | **un** argumento: `"--dato=2"` | -| **Separada por espacio** | `--dato 2` | **dos** argumentos: `"--dato"` y `"2"` | - -**Analogía.** Es como decir *"una Coca-Cola grande"* vs. *"una Coca-Cola, grande"*. Mismo significado, -forma distinta. - -> **Momento ajá.** Esto es central. Muchos programas aceptan **las dos formas**. Cuando parseás a mano, -> o contemplás ambas, o al menos **documentás** cuál aceptás. - -## 6.2 Forma 1: con `=` (todo en un argumento) - -Acá el valor vive **dentro** del mismo argumento, después del `=`. - -```c -/* ¿el argumento empieza con "--dato="? */ -if (strncmp(argv[i], "--dato=", 7) == 0) { - char *valor = argv[i] + 7; /* lo que viene después del '=' */ - /* 'valor' apunta a "2" */ -} -``` - -Desarmemos el `+ 7`: `"--dato="` mide **7** caracteres (`-`, `-`, `d`, `a`, `t`, `o`, `=`). Sumar 7 al -puntero lo corre justo **hasta el primer carácter del valor**. Eso es **aritmética de punteros**: si -`argv[i]` apunta al inicio de la cadena, `argv[i] + 7` apunta al octavo carácter. - -> **Para guardar.** `strncmp` compara solo los primeros `n` caracteres. Lo usamos para preguntar -> "¿empieza con `--dato=`?" sin importar qué sigue. Después tomamos el resto con `+ 7`. - -## 6.3 Forma 2: separada por espacio (valor en el argumento siguiente) - -Acá el valor está en **el siguiente** elemento de `argv`. - -```c -/* ¿el argumento es exactamente "--dato" y hay un siguiente? */ -if (strcmp(argv[i], "--dato") == 0 && (i + 1) < argc) { - char *valor = argv[i + 1]; /* el valor es el argumento siguiente */ - i++; /* ¡salto el valor para no reprocesarlo! */ - /* usar 'valor' */ -} -``` - -Los dos detalles que importan: - -1. **`(i + 1) < argc`**: comprobar que **existe** un argumento siguiente. Si no, el usuario escribió - `--dato` al final y **falta** el valor. -2. **`i++`**: avanzar un paso extra para que el bucle **no vuelva a procesar** el valor `"2"` como si - fuera un argumento independiente. - -> **Trampa N.º 3 — comerse el argumento siguiente.** Si te olvidás del `i++`, en la próxima vuelta del -> bucle vas a ver `"2"` y lo vas a clasificar como **posicional** (o peor). El valor de una opción -> **no** es un argumento suelto: es parte de la opción. Avanzá un lugar. - -## 6.4 ¿Y si el valor es un número? - -`argv` siempre guarda **texto**. Si la opción representa un número, hay que **convertirlo**: - -- `atoi("42")` → `42` (cómodo, pero no avisa si la cadena **no** era un número). -- `strtol("42", &fin, 10)` → más robusto: te deja **verificar** que toda la cadena se convirtió. -- `strtod` para números con decimales. - -Elegir bien la conversión es parte de la Parte 7 (robustez). - -## 6.5 Detectar que falta el valor - -Si el usuario escribe `--dato` **y no pone** el valor (forma separada), tenés que **detectarlo** y -avisar. Ese chequeo es el `(i + 1) < argc` del ejemplo: si es falso, el valor **falta**. Ahí se muestra -un error claro y, normalmente, se termina con código de error (Parte 7). - -## Práctica de la Parte 6 - -### Ejercicio 13.9 — Opción con valor (`--max=N`) - -**Conceptos** · `strncmp`, `atoi`, aritmética de punteros - -**Contexto** · Muchas herramientas aceptan límites configurables. Simplificación: leer un máximo. - -**Consigna** · Escribí un programa que detecte la opción `--max=N` (forma pegada con `=`). Si está -presente, muestra `Máximo: N`. Si no está, muestra `Máximo: sin definir`. - -**Entrada** · Argumentos por línea de comandos. -**Salida** · La línea del ejemplo. - -**Escenario de prueba** - -```text -$ ./limite --max=10 -Máximo: 10 -``` - -```text -$ ./limite -Máximo: sin definir -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i`, `maxValue` - -**Restricciones / pistas** -- Detectá el prefijo `--max=` con `strncmp`. -- Extraé el valor sumando al puntero la longitud del prefijo. -- Si no aparece la opción, el valor queda "sin definir" (una bandera booleana te ayuda). - -**Criterios de aceptación** -- [ ] Detecta la forma con `=`. -- [ ] Muestra el valor correcto. -- [ ] Sin la opción, muestra `sin definir`. -- [ ] No crashea si el programa se ejecuta sin argumentos. - -### Ejercicio 13.10 — Opción con valor separado (`--max 10`) - -**Conceptos** · `strcmp`, índice siguiente, `i++` - -**Contexto** · Otra convención muy común: opción y valor separados por espacio. Simplificación: el -mismo máximo, ahora en dos argumentos. - -**Consigna** · Igual que 13.9, pero aceptando la **forma separada por espacio**: `--max 10`. - -**Entrada** · Argumentos por línea de comandos. -**Salida** · La misma que 13.9. - -**Escenario de prueba** - -```text -$ ./limite --max 10 -Máximo: 10 -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i`, `maxValue` - -**Restricciones / pistas** -- Compará la cadena completa con `strcmp`. -- Verificá que exista el argumento siguiente (`i + 1 < argc`). -- No olvides el `i++` para saltar el valor. - -**Criterios de aceptación** -- [ ] Acepta la forma separada por espacio. -- [ ] Salta el valor consumido (`i++`). -- [ ] Verifica que el valor exista antes de usarlo. - -### Ejercicio 13.11 — Aceptar las dos formas - -**Conceptos** · combinación de `strncmp` y `strcmp`, control de flujo - -**Contexto** · Las herramientas profesionales aceptan múltiples formas de escribir lo mismo. -Simplificación: unir la forma pegada y la separada. - -**Consigna** · Mejorá 13.9 y 13.10 en **un solo** programa que acepte **tanto `--max=10` como -`--max 10`**. - -**Entrada** · Argumentos por línea de comandos. -**Salida** · Igual que 13.9. - -**Escenario de prueba** - -```text -$ ./limite --max=10 -Máximo: 10 -``` - -```text -$ ./limite --max 10 -Máximo: 10 -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i`, `maxValue`, `haveMax` - -**Restricciones / pistas** -- Podés preguntar primero por el prefijo `--max=` y, si no coincide, por la igualdad exacta - `--max`. -- Evitá duplicar la conversión: una sola vez que tengas el texto del valor, convertilo. - -**Criterios de aceptación** -- [ ] Funciona con `=` y con espacio. -- [ ] No hay comportamiento distinto entre las dos formas (mismo resultado). -- [ ] El código no repite innecesariamente la lógica de conversión. - -### Ejercicio 13.12 — Detectar valor faltante - -**Conceptos** · control de errores, `stderr`, terminación con código - -**Contexto** · Un programa serio no se cuelga ni crashea si al usuario se le olvida el valor. Avisa y -termina. Simplificación: error de argumento incompleto. - -**Consigna** · Partiendo de 13.10, si el usuario escribe `--max` **sin valor** (al final de la línea), -el programa debe mostrar un error **y terminar**. No debe seguir como si nada. - -**Entrada** · Argumentos por línea de comandos. -**Salida** · Mensaje de error y línea de uso. - -**Escenario de prueba** - -```text -$ ./limite --max -Error: la opción --max necesita un valor -Uso: ./limite [--max= | --max ] -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i`, `ok` - -**Restricciones / pistas** -- El caso es `i + 1 >= argc` cuando la opción es exactamente `--max`. -- El mensaje de error va por la **salida de errores** (`stderr`), no por la salida normal. -- Terminá el programa con un código **distinto de 0** para señalar el fallo (Parte 7). - -**Criterios de aceptación** -- [ ] Detecta `--max` sin valor. -- [ ] Muestra un error claro (sin crashear). -- [ ] Incluye una línea de uso. -- [ ] Termina con código distinto de 0. - ---- - -# PARTE 7 — Validar, avisar y salir con estilo - -## 7.1 La regla de oro - -**"Fracasar con claridad es mejor que romperse en silencio."** - -Un buen programa de consola, ante una entrada inválida: - -1. **Avisa** qué pasó, en un mensaje claro; -2. **Muestra** cómo se usa; -3. **Termina** con un código que indique el error. - -## 7.2 Códigos de salida - -El `return` de `main` es lo que el **shell** (y cualquier script) ve como **código de salida**: - -- **`0`** → éxito (es el único que significa "todo bien"). -- **Distinto de 0** → error (`1`, `2`, etc.). - -Por eso, cuando algo falla: `return 1;`. Quien ejecutó tu programa puede **decidir** en función de eso -(un `if` en un script, un `&&` en la terminal, una tarea automatizada). - -> **Momento ajá.** El código de salida **no** es un capricho: es la forma en que un programa le -> **cuenta** a otro si salió bien. Es la versión "de consola" de un booleano. - -## 7.3 Errores por `stderr`, resultados por `stdout` - -Esto es un detalle **profesional** que marca la diferencia: - -- **Resultados normales** → `stdout` (`printf`). -- **Errores, avisos y ayudas** → `stderr` (`fprintf(stderr, ...)`). - -**¿Por qué?** Porque si alguien guarda la salida normal en un archivo: - -```bash -./programa datos.txt > resultado.txt -``` - -los **errores no deben** colarse en `resultado.txt`. `stdout` va al archivo; `stderr` **sigue -apareciendo** en la pantalla, separado. Los datos siguen limpios. - -```c -fprintf(stderr, "Error: no se encontró el archivo '%s'\n", nombre); -``` - -> **Puente al capítulo siguiente.** `stdin`, `stdout` y `stderr` son las **tres tuberías** de todo -> programa. Acá las usamos apenas; las vas a entender a fondo en -> `14-entrada-salida-estandar`. Por ahora, quedate con la regla: **errores a `stderr`, resultados a -> `stdout`**. - -## 7.4 El mensaje de uso (`--help`) - -Toda herramienta que acepta opciones debería tener `-h`/`--help`. Debe ser **conciso, claro** y usar -`argv[0]`. Un buen formato de referencia: - -```text -Uso: ./programa [opciones] [archivo_salida] -Opciones: - -h, --help Muestra esta ayuda - -v, --verbose Activa modo detallado - -o, --output Especifica archivo de salida - --max Máximo de elementos (por defecto: 100) -``` - -La convención de `[]` y `<>` es vieja y muy útil: - -- `<>` → **obligatorio** (``). -- `[]` → **opcional** (`[archivo_salida]`, `[opciones]`). - -## 7.5 Validar argumentos obligatorios - -Si tu programa **necesita** un archivo, comprobá que esté: - -```c -if (argc < 2) { - fprintf(stderr, "Error: falta el archivo de entrada\n"); - fprintf(stderr, "Uso: %s \n", argv[0]); - return 1; -} -``` - -Ese `argc < 2` significa: "solo vino el nombre del programa, no hay ningún dato". - -## Práctica de la Parte 7 - -### Ejercicio 13.13 — Archivo obligatorio + ayuda - -**Conceptos** · `argc`, `strcmp`, `stderr`, códigos de salida, validación - -**Contexto** · Un procesador de archivos real exige un archivo y ofrece ayuda. Simplificación: la -misma estructura, sin procesar nada de verdad. - -**Consigna** · Creá un programa que: - -1. Si recibe `-h` o `--help` → muestre la ayuda y retorne `0`. -2. Si **no** recibe ningún argumento extra → muestre un error por `stderr`, la línea de uso, y - retorne `1`. -3. Si recibe un nombre de archivo (posicional) → muestre `Procesando: ` por `stdout` y - retorne `0`. - -**Entrada** · Argumentos por línea de comandos. -**Salida** · Según el caso, ayuda, error o confirmación. - -**Escenario de prueba** - -```text -$ ./procesar --help -Uso: ./procesar [opciones] -Opciones: - -h, --help Muestra esta ayuda -``` - -```text -$ ./procesar -Error: se requiere un archivo -Uso: ./procesar [opciones] -``` - -```text -$ ./procesar datos.txt -Procesando: datos.txt -``` - -**Identificadores sugeridos** · `argc`, `argv`, `i`, `fileName` - -**Restricciones / pistas** -- La ayuda tiene **prioridad**: detectala **antes** de validar el obligatorio. -- Los **errores** van a `stderr`; la confirmación normal a `stdout`. -- Usá `argv[0]` en los mensajes de uso. - -**Criterios de aceptación** -- [ ] La ayuda se muestra con `-h` y con `--help`, y retorna `0`. -- [ ] Sin argumentos, el error va a `stderr` y retorna `1`. -- [ ] Con archivo, confirma por `stdout` y retorna `0`. -- [ ] Los mensajes usan `argv[0]`. -- [ ] Los códigos de salida son los correctos. - -### Ejercicio 13.14 — Validar que el valor sea numérico - -**Conceptos** · `strtol`/`isdigit`, validación, `stderr` - -**Contexto** · `--max=abc` no tiene sentido: el máximo debe ser un número. El programa debe avisarlo. -Simplificación: validación de un argumento numérico. - -**Consigna** · Ampliá el programa del 13.11: si `--max` recibe un valor que **no** es un entero válido, -mostrá un error y terminá con código de error. Si es válido, mostrá el máximo. - -**Entrada** · Argumentos por línea de comandos. -**Salida** · El máximo o un error. - -**Escenario de prueba** - -```text -$ ./limite --max=abc -Error: '--max' requiere un número entero -``` - -```text -$ ./limite --max=42 -Máximo: 42 -``` - -**Identificadores sugeridos** · `maxText`, `maxValue`, `end` - -**Restricciones / pistas** -- Una forma robusta: `strtol` y comprobar que **consumió toda** la cadena (`*end == '\0'`) y que no - hubo desbordamiento. -- El error va a `stderr` y el programa retorna distinto de 0. -- **No** uses `atoi` solo: no distingue `"42"` de `"42abc"` ni de `"abc"`. - -**Criterios de aceptación** -- [ ] `--max=abc` produce un error claro por `stderr`. -- [ ] `--max=42` muestra `Máximo: 42`. -- [ ] Un valor parcialmente numérico (`--max=42x`) se rechaza. -- [ ] El programa no crashea y retorna el código correcto. - ---- - -# PARTE 8 — Orden y robustez: `getopt` - -## 8.1 Por qué existe - -Parsear a mano es **perfecto para aprender** (y lo que hiciste hasta acá lo prueba). Pero cuando hay -**muchas** opciones, o querés soportar `-abc` apiladas y detectar faltantes de forma consistente, el -código a mano se vuelve repetitivo y frágil. - -Para eso existe **`getopt()`**, una función estándar (POSIX) que hace ese trabajo por vos. - -> **Para entender antes de usar.** **No saltees las Partes 4 a 7.** Dominar el parseo manual es lo que -> te permite entender **qué hace** `getopt` por dentro, y detectar errores cuando algo no cuadra. - -## 8.2 Qué resuelve `getopt` - -- Maneja opciones **cortas** (`-v`, `-h`, `-o valor`). -- Soporta opciones **apiladas** (`-abc` = `-a -b -c`). -- Sabe cuáles opciones **requieren un valor** y cuáles no. -- Avisa de **opciones desconocidas** y de **valores faltantes**. -- Deja los **posicionales juntos** al final, en `optind` (te lo explico en 8.4). - -## 8.3 La forma de usarlo - -```c -#include /* getopt */ -#include - -int main(int argc, char *argv[]) -{ - int opt; - - /* optstring: letras de opciones cortas. - Un ':' DESPUÉS de una letra significa "esa opción requiere un valor". */ - while ((opt = getopt(argc, argv, "hvf:o:")) != -1) { - switch (opt) { - case 'h': /* -h sin valor */ break; - case 'v': /* -v sin valor */ break; - case 'f': /* -f */ break; - case 'o': /* -o */ break; - case '?': /* opción desconocida o valor faltante */ break; - default: break; - } - } - return 0; -} -``` - -Desarmemos el `"hvf:o:"`: - -| Letra en `optstring` | Significa | -|---|---| -| `h` | opción `-h` (sin valor) | -| `v` | opción `-v` (sin valor) | -| `f:` | opción `-f` que **requiere** un valor (el `:` va **después** de la letra) | -| `o:` | opción `-o` que **requiere** un valor | - -El valor de una opción con `:` queda en la variable global **`optarg`** (`char *`). - -> **Momento ajá.** El `:` va **a la derecha** de la letra que necesita argumento. Leer `"f:"` es decir -> "la opción `-f` lleva un valor". Leer `"hf:"` es decir "`-h` sin valor, `-f` con valor". - -## 8.4 Los posicionales después de `getopt` - -Cuando `getopt` termina (devuelve `-1`), todas las opciones ya se procesaron. La variable global -**`optind`** apunta al **primer argumento que no es opción**. De ahí en adelante, todo es posicional: - -```c -while ((opt = getopt(argc, argv, "hv")) != -1) { /* ... */ } - -for (int i = optind; i < argc; i++) { - printf("Posicional: %s\n", argv[i]); -} -``` - -`getopt` **reordena** internamente la línea para dejarte las opciones primero y los posicionales -después. Vos solo leés desde `optind`. - -## 8.5 Errores que detecta - -- **Opción desconocida:** `getopt` devuelve `'?'` (y suele imprimir un aviso por `stderr`). -- **Valor faltante:** si una opción con `:` aparece sin su valor, también se señala con `'?'` (o `:` - según configuración). Lo habitual es tratar `'?'` mostrando tu propio mensaje de uso, más claro para - el lector. - -## 8.6 Disponibilidad - -`getopt` viene de ``, típico de Linux y macOS. En Windows, con MinGW/GCC o entornos POSIX -(como Git Bash / MSYS2), suele estar disponible. Si compilás con MSVC puro, no; ahí este capítulo te -sirve igual porque ya sabés hacerlo a mano. - -## Práctica de la Parte 8 - -### Ejercicio 13.15 — `getopt` básico - -**Conceptos** · `getopt`, `optstring`, `switch`, opciones apiladas - -**Contexto** · Reescribir el parseo con la función estándar. Simplificación: tres opciones cortas. - -**Consigna** · Usando `getopt`, interpretá: - -- `-h` → mostrá la ayuda y terminá; -- `-v` → activá modo detallado (informá `Modo verbose: ON`); -- `-f ` → requiere un archivo (`Archivo: `). - -**Entrada** · Argumentos por línea de comandos. -**Salida** · Según las opciones recibidas. - -**Escenario de prueba** - -```text -$ ./prog -v -f datos.txt -Modo verbose: ON -Archivo: datos.txt -``` - -```text -$ ./prog -vf datos.txt -Modo verbose: ON -Archivo: datos.txt -``` - -**Identificadores sugeridos** · `opt`, `optstring`, `optarg` - -**Restricciones / pistas** -- El `optstring` correcto para esto es `"hvf:"`. -- El segundo escenario prueba que **`-vf` apiladas** funciona sin código extra (lo hace `getopt`). -- Para `-h`, terminá el programa dentro del `case`. - -**Criterios de aceptación** -- [ ] Usa `getopt` con `optstring` correcto. -- [ ] Acepta `-v` y `-f` juntas (`-vf`). -- [ ] `-f` toma su valor de `optarg`. -- [ ] `-h` muestra ayuda y termina. - -### Ejercicio 13.16 — Posicionales con `getopt` - -**Conceptos** · `optind`, posicionales, orden de argumentos - -**Contexto** · Separar las opciones de los datos "de verdad". Simplificación: listar los archivos -sueltos después de las opciones. - -**Consigna** · Ampliá 13.15 para que, **después** de procesar las opciones, muestre todos los -posicionales restantes (los que quedan desde `optind`). - -**Entrada** · Mezcla de opciones y posicionales. -**Salida** · El efecto de las opciones y luego la lista de posicionales. - -**Escenario de prueba** - -```text -$ ./prog -v entrada.txt salida.txt extra.txt -Modo verbose: ON -Posicionales: - entrada.txt - salida.txt - extra.txt -``` - -**Identificadores sugeridos** · `opt`, `optind`, `i` - -**Restricciones / pistas** -- Recorré desde `optind` hasta `argc-1`. -- No mezcles: las opciones se procesan en el `while`; los posicionales, **después**. - -**Criterios de aceptación** -- [ ] Usa `optind` para ubicar el primer posicional. -- [ ] Lista todos los posicionales en orden. -- [ ] Las opciones no se mezclan con la lista. - ---- - -# PARTE 9 — Nivel pro: `getopt_long` - -## 9.1 El salto - -Ahora sí, **nivel pro**: opciones **largas** (`--verbose`, `--help`, `--output=...`, `--max 10`). - -Para eso existe **`getopt_long()`** (también POSIX, header ``). Es **el estándar** que usan -herramientas como `ls`, `grep` y `gcc`. Todo lo que aprendiste de `getopt` sigue valiendo: -`getopt_long` es su hermano mayor, que además entiende las palabras largas. - -## 9.2 Comparación rápida - -| Característica | `getopt` | `getopt_long` | -|---|---|---| -| Opciones cortas (`-v`) | Sí | Sí | -| Opciones largas (`--verbose`) | **No** | **Sí** | -| Acepta `--opcion=valor` | No | **Sí** | -| Acepta `--opcion valor` | No | **Sí** | -| Manejo de `-abc` apiladas | Sí | Sí | - -## 9.3 Las tres piezas - -Para usar `getopt_long` necesitás **tres** cosas: - -1. incluir ``; -2. declarar una **tabla** de opciones largas (`struct option`); -3. llamar a **`getopt_long`** en lugar de `getopt`. - -## 9.4 La tabla de opciones largas - -```c -#include - -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 obligatorio */ -}; -``` - -Cada fila tiene **cuatro columnas**: - -1. **Nombre largo** (`"help"`) → se reconoce como `--help`. -2. **¿Lleva valor?** → `no_argument` (flag) o `required_argument` (con valor). -3. **¿A dónde apunta el resultado?** → `0` significa "devolvé el valor de la cuarta columna". -4. **Código corto asociado** → `'h'`, `'v'`, `'o'`, `'m'`. Así la opción larga **y** la corta caen en - el **mismo `case`** del `switch`. - -La última fila `{0,0,0,0}` es el **terminador**: le dice a `getopt_long` dónde acaba la tabla. **No la -olvides** (es el bug clásico de esta función). - -> **Momento ajá.** Gracias a la cuarta columna, `--verbose` y `-v` comparten el `case 'v'`. Escribís -> el comportamiento **una sola vez** y las dos formas lo activan. Eso es elegancia, no magia. - -## 9.5 Cómo se llama - -```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 (optarg) */break; - case 'm': /* -m o --max (optarg) */ break; - case '?': /* desconocida / falta valor */ break; - default: break; - } -} -``` - -El primer string (`"hvo:m:"`) sigue siendo el `optstring` de las opciones **cortas**. La tabla es para -las **largas**. Y `getopt_long` entiende, sin costo extra: - -- `--output=archivo.txt` (pegado con `=`); -- `--output archivo.txt` (separado por espacio). - -Para las largas, el valor llega —igual que siempre— en **`optarg`**. - -## 9.6 Errores - -- Opción larga **desconocida** → `'?'`; -- Opción larga que **requiere valor** y no lo tiene → `'?'`; -- Opción corta **desconocida** → `'?'`. - -Como en `getopt`, lo más limpio es manejar `'?'` mostrando **tu** mensaje de uso y retornando el -código de error. - -## Práctica de la Parte 9 - -### Ejercicio 13.17 — (JEFE) Utilidad de consola completa - -**Conceptos** · `getopt_long`, `struct option`, `optarg`, `optind`, `stderr`, códigos de salida, -validación numérica, ayuda. - -**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: - -| 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. Acepte las **dos formas** de valor para las largas (`--output=x` y `--output x`). -2. Trate los argumentos **no-opción** como archivos posicionales (listados al final). -3. Valide que `--max` sea un entero; si no, error por `stderr` y retorno distinto de 0. -4. Ante una opción desconocida, muestre un error por `stderr`, la línea de uso y retorne distinto de 0. -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 -Máximo: 10 -Posicionales: - entrada.txt - salida.txt -``` - -```text -$ ./util --max -Error: la opción --max necesita un valor -Uso: ./util [opciones] ... -``` - -```text -$ ./util --max=abc -Error: '--max' requiere un número entero -``` - -```text -$ ./util --desconocida -Error: opción desconocida -Uso: ./util [opciones] ... -``` - -**Identificadores sugeridos** · `opt`, `long_options`, `option_index`, `optind`, `verbose`, -`max_value`, `output_name`, `have_output`, `i` - -**Restricciones / pistas** -- Usá `getopt_long` con el `optstring` `"hvo:m:"` (o el que corresponda a tus short options). -- **No** olvides el terminador `{0,0,0,0}` de la tabla. -- El `-h` tiene **prioridad**: mostralo apenas aparece y terminá. -- Validá `--max` con `strtol` comprobando que se consumió **toda** la cadena. -- Errores → `stderr`; resultados → `stdout`. -- Los posicionales salen de `argv[optind..argc-1]`. - -**Criterios de aceptación** -- [ ] Reconoce `-v`/`--verbose`, `-o`/`--output` y `-m`/`--max`. -- [ ] Acepta valor pegado (`--max=10`) y separado (`--max 10`). -- [ ] `--help`/`-h` muestra ayuda y retorna `0`. -- [ ] `--max` sin valor y `--max=abc` producen errores claros por `stderr` con código de retorno != 0. -- [ ] Una opción desconocida produce error por `stderr` y código != 0. -- [ ] Los posicionales se listan después de las opciones (usando `optind`). -- [ ] Los resultados van a `stdout` y los errores a `stderr`. -- [ ] No hay accesos fuera de rango ni bucles infinitos. - ---- - -## Cierre: lo que te llevás - -Si tuvieras que quedarte con **una sola idea**, que sea esta: **tu programa puede recibir sus órdenes -al nacer**, y esas órdenes llegan como una lista de cadenas que se llama `argv`. - -- **`argc` cuenta, `argv` guarda.** `argc` es cuántos argumentos hay (incluido el nombre); `argv` es la - lista, con `argv[0]` = el nombre del programa. -- **El espacio separa; las comillas agrupan.** `"hola mundo"` entre comillas es **un** argumento. -- **Recorré con `i < argc`.** Nunca toques `argv[argc]`: es la trampa N.º 1. -- **Aprendé a clasificar:** posicional (sin `-`), opción corta (`-x`), opción larga (`--xx`), y opción - con valor (`--xx=valor` o `--xx valor`). -- **Ojo con la trampa N.º 3:** cuando el valor viene separado por espacio, avanzá un índice (`i++`) - para no reprocesarlo. -- **Fracasá con claridad:** errores por `stderr`, ayuda con `argv[0]`, y `return` distinto de 0 ante - error. -- **Dominá lo manual primero** (Partes 4–7); recién después **delegate** en `getopt` y `getopt_long`, - que te dan orden, opciones apiladas y soporte de `--largas` con valor. - -Y el puente hacia lo que viene: hasta ahora viste **cómo entra la orden** (por la línea de comandos). -En `14-entrada-salida-estandar` vas a aprender **cómo entra y sale todo lo demás**: las tres tuberías -`stdin`/`stdout`/`stderr`, la lectura por teclado, los búferes y `EOF`. Los argumentos te dijeron qué -hacer; el capítulo siguiente te enseña a **conversar** con el programa mientras corre. diff --git a/book/13-getopt-long.md b/book/13-getopt-long.md new file mode 100644 index 0000000..93b1f8b --- /dev/null +++ b/book/13-getopt-long.md @@ -0,0 +1,1373 @@ +# 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 +#include + +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 ` +- 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 ``. +- [ ] 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 ``; +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 +#include + +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] \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 +#include + +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 +#include + +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] ...\n", argv[0]); + return 0; + case 'v': + verbose = 1; + break; + case 'o': + output_file = optarg; + break; + case '?': + fprintf(stderr, "Uso: %s [-hv] [-o archivo] ...\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 +#include + +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] ...\n", argv[0]); + return 0; + case 'v': + verbose = 1; + break; + case 'm': + max = atoi(optarg); + break; + case '?': + fprintf(stderr, "Uso: %s [-hv] [-m N] ...\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: `, 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: +Opciones leídas: -n/-verbose, --help=0 +Archivos a procesar: + Archivo 1: + Archivo 2: +``` + +**Criterios de aceptación** +- [ ] Incluye `` 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 `` o `` +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] +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] +``` + +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]` | +| `` | Los angulares indican un argumento **obligatorio**. | `` | +| `palabra` | Sin nada: lo que se escribe tal cual, como `--help`. | `--verbose` | + +La estructura típica es siempre: + +``` +Uso: [opciones] +``` + +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 Guarda el resultado en el archivo indicado. + -m, --max Número máximo de elementos a procesar (por defecto: 100). +``` + +Notá que las opciones con valor incluyen `` 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 + +/* 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] \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 Guarda el resultado en el archivo indicado.\n"); + printf(" -m, --max 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 `` 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 `. +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: desde ` por `stdout` y retorne 0. + +**Escenario de prueba** + +```text +$ ./conector --help +Uso: ./conector [opciones] [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 Especifica la base de datos a conectar. +``` + +```text +$ ./conector +Error: se requiere un archivo de configuración +Uso: ./conector [opciones] [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] ... +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 de salida. + -m, --max 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. + diff --git a/dist/13-getopt-long.pdf b/dist/13-getopt-long.pdf new file mode 100644 index 0000000..324cd5f Binary files /dev/null and b/dist/13-getopt-long.pdf differ