1445 lines
50 KiB
Markdown
1445 lines
50 KiB
Markdown
# 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 <archivo>\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 <archivo> [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 <stdio.h>
|
||
|
||
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] <archivo>
|
||
Opciones:
|
||
-h, --help Muestra esta ayuda
|
||
-v, --verbose Modo detallado
|
||
```
|
||
|
||
```text
|
||
$ ./ayuda --help
|
||
Uso: ./ayuda [opciones] <archivo>
|
||
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=<n> | --max <n>]
|
||
```
|
||
|
||
**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_entrada> [archivo_salida]
|
||
Opciones:
|
||
-h, --help Muestra esta ayuda
|
||
-v, --verbose Activa modo detallado
|
||
-o, --output <file> Especifica archivo de salida
|
||
--max <n> Máximo de elementos (por defecto: 100)
|
||
```
|
||
|
||
La convención de `[]` y `<>` es vieja y muy útil:
|
||
|
||
- `<>` → **obligatorio** (`<archivo_entrada>`).
|
||
- `[]` → **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 <archivo>\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: <archivo>` 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] <archivo>
|
||
Opciones:
|
||
-h, --help Muestra esta ayuda
|
||
```
|
||
|
||
```text
|
||
$ ./procesar
|
||
Error: se requiere un archivo
|
||
Uso: ./procesar [opciones] <archivo>
|
||
```
|
||
|
||
```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 <unistd.h> /* getopt */
|
||
#include <stdio.h>
|
||
|
||
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 <archivo> */ break;
|
||
case 'o': /* -o <archivo> */ 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 `<unistd.h>`, 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 <archivo>` → requiere un archivo (`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 `<getopt.h>`). 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 `<getopt.h>`;
|
||
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 <getopt.h>
|
||
|
||
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] <archivo>...
|
||
```
|
||
|
||
```text
|
||
$ ./util --max=abc
|
||
Error: '--max' requiere un número entero
|
||
```
|
||
|
||
```text
|
||
$ ./util --desconocida
|
||
Error: opción desconocida
|
||
Uso: ./util [opciones] <archivo>...
|
||
```
|
||
|
||
**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.
|