Los parámetros main(int argc, char *argv[])
¶
Cuando ejecutás un programa en C desde la terminal, tenés la posibilidad de pasarle información directamente en la línea de comandos. Esta información, conocida como “argumentos”, es recibida por la función main
a través de dos parámetros especiales: argc
y argv
.
Entender cómo manipular estos parámetros es fundamental para crear herramientas de línea de comandos flexibles y potentes. Y aunque es opcional, entender como funciona la terminal termina siendo importante para lograr que nuestros programas se integren a la perfección.
La signatura estándar de la función main
que acepta argumentos es la siguiente:
int main(int argc, char *argv[]) {
// Tu código aquí
return EXIT_SUCCESS;
}
Estos dos parámetros contienen toda la información que se pasa al programa en el momento de su ejecución.
Table 1:Descripción de los parámetros
Parámetro | Tipo | Descripción |
---|---|---|
|
| Argument Count: Es un entero que contiene el número total de argumentos pasados al programa. Su valor es siempre al menos 1, ya que el primer argumento es el propio nombre del ejecutable. |
|
| Argument Values: Es un array de punteros a cadenas de caracteres (strings). Cada elemento del array apunta a uno de los argumentos.
|
Figure 1:Estructura en memoria de argc
y argv[]
. El arreglo argv
contiene punteros a cadenas de caracteres (strings), cada una representando un argumento pasado al programa.
Ejemplo Básico: Imprimir Todos los Argumentos¶
El programa más simple para entender su funcionamiento es uno que recorre los
valores en argv
e imprime cada uno de sus elementos.
#include <stdio.h>
#include <stdlib.h> // Para EXIT_SUCCESS
int main(int argc, char *argv[]) {
printf("El programa se ejecutó con el nombre: %s\n", argv[0]);
printf("Número total de argumentos: %d\n", argc);
printf("----------------------------------------\n");
// Iteramos desde el argumento 1, ya que el 0 es el nombre del programa
if (argc > 1) {
printf("Los argumentos proporcionados son:\n");
for (int i = 1; i < argc; i++) {
printf(" Argumento %d: %s\n", i, argv[i]);
}
} else {
printf("No se proporcionaron argumentos adicionales.\n");
}
return EXIT_SUCCESS;
}
Program 1:“Programa que itera e imprime sus argumentos.”
Compilación y ejecución¶
Si guardás el código anterior como main_args.c
y lo compilás, podés ver la
salida al ejecutarlo con distintos argumentos.
# Compilamos el programa
gcc main_args.c -o mi_programa
# Ejecutamos sin argumentos
./mi_programa
# Ejecutamos con varios argumentos
./mi_programa hola "mundo con espacios" 42
Salida de la segunda ejecución:
$> ./mi_programa hola "mundo con espacios" 42
El programa se ejecutó con el nombre: ./mi_programa
Número total de argumentos: 4
----------------------------------------
Los argumentos proporcionados son:
Argumento 1: hola
Argumento 2: mundo con espacios
Argumento 3: 42
Procesamiento de opciones¶
Las opciones (también llamadas flags o switches) son argumentos especiales
que modifican el comportamiento de un programa. Usualmente comienzan con un
guion (-
) o dos (--
), como -h
o --help
.
Para detectarlas, necesitás comparar las cadenas de argv
con los valores que
esperás. La función strcmp
(ver strcmp
, strstr
y strchr
) de la biblioteca string.h
es ideal para esto.
#include <stdio.h>
#include <string.h> // Necesario para strcmp
#include <stdlib.h> // Para EXIT_SUCCESS
int main(int argc, char *argv[]) {
// Verificamos si el primer argumento es -h o --help
if (argc == 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
printf("Uso: %s [opciones] [argumentos]\n", argv[0]);
printf("Este es un programa de ejemplo para mostrar ayuda.\n");
return EXIT_SUCCESS; // Terminamos la ejecución después de mostrar la ayuda
}
printf("Programa ejecutándose normalmente.\n");
// ... resto de la lógica del programa ...
return EXIT_SUCCESS;
}
Program 2:“Detección de una opción -h
para mostrar ayuda.”
Conversión de Argumentos a Números¶
Un punto crucial es que todos los argumentos en argv
son cadenas de
caracteres, incluso si parecen números (como "42"
). Para poder realizar
operaciones matemáticas con ellos, debés convertirlos a un tipo numérico (como
int
o double
).
La biblioteca estándar de C (stdlib.h
) provee funciones para esta tarea, como
atoi
y, de forma más robusta, strtol.
#include <stdio.h>
#include <stdlib.h> // Necesario para atoi y EXIT_SUCCESS/FAILURE
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Error: Proporcioná al menos un número para sumar.\n");
fprintf(stderr, "Uso: %s num1 num2 ...\n", argv[0]);
return EXIT_FAILURE; // Salimos un código de error
}
int suma = 0;
for (int i = 1; i < argc; i++) {
// atoi convierte la cadena a un entero
int numero = atoi(argv[i]);
suma = suma + numero;
}
printf("La suma total es: %d\n", suma);
return EXIT_SUCCESS;
}
Program 3:“Programa que suma los números pasados como argumentos.”
Compilación y Ejecución del Sumador¶
# Compilamos
gcc sumador.c -o sumador
# Ejecutamos con números
./sumador 10 20 5
# Salida: La suma total es: 35
Funciones Útiles de la Biblioteca Estándar¶
Para construir programas de línea de comandos robustos, es indispensable conocer algunas de las herramientas que provee la biblioteca estándar de C. A continuación, se mencionan algunas de las funciones más relevantes en este contexto.
Funciones Clave¶
Conversión de Cadenas (
<stdlib.h>
):strtol: Convierte cadenas a números de forma segura, permitiendo un control de errores robusto.
atof
: Convierte cadenas adouble
.
Control del Programa (
<stdlib.h>
):Generación de Números Aleatorios (
<stdlib.h>
):Manipulación de Cadenas (
<string.h>
):Funciones Matemáticas (
<math.h>
):Si tus argumentos son números, probablemente necesites esta biblioteca para operaciones como pow, sqrt, log, o funciones trigonométricas (ver Funciones Trigonométricas). No olvides compilar con la bandera
-lm
.
Dominar el uso de argc
y argv
junto con estas funciones te permitirá crear aplicaciones de consola complejas y útiles, una habilidad fundamental en el mundo del desarrollo de software.
Interacción con el Shell¶
Cuando ejecutás un programa desde la línea de comandos, tu programa no existe en aislamiento sino que forma parte de un ecosistema más amplio: el shell o intérprete de comandos (como bash
, zsh
, o sh
). El shell proporciona mecanismos poderosos para conectar programas entre sí y controlar el flujo de datos, lo que convierte a los programas C en herramientas componibles dentro de un sistema más grande.
Códigos de Salida¶
Cada programa retorna un código de salida (o exit status) al shell cuando termina su ejecución. Por convención en Unix y Linux:
0
indica éxito (EXIT_SUCCESS
)Cualquier valor distinto de cero indica un error (
EXIT_FAILURE
generalmente es1
)
Este código es fundamental para que el shell y otros programas sepan si tu programa funcionó correctamente.
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Error: falta un argumento\n");
return EXIT_FAILURE; // Retornamos 1 al shell
}
printf("Procesando: %s\n", argv[1]);
return EXIT_SUCCESS; // Retornamos 0 al shell
}
Desde el shell, podés inspeccionar el código de salida del último programa ejecutado:
./mi_programa argumento
echo $? # Imprime el código de salida (0 si éxito)
Variables de Entorno¶
El shell mantiene un conjunto de variables de entorno que los programas pueden leer. Estas variables configuran el comportamiento del sistema y almacenan información útil como rutas de búsqueda, configuraciones del usuario, etc.
La función getenv permite leer estas variables:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *home = getenv("HOME");
char *usuario = getenv("USER");
if (home != NULL) {
printf("Directorio home: %s\n", home);
}
if (usuario != NULL) {
printf("Usuario actual: %s\n", usuario);
}
return EXIT_SUCCESS;
}
Variables comunes incluyen PATH
(rutas de búsqueda de ejecutables), HOME
(directorio del usuario), USER
(nombre del usuario), y LANG
(configuración de idioma).
Figure 2:Códigos de salida en programas C. El shell usa estos códigos para determinar si la ejecución fue exitosa (0
) o falló (distinto de 0
).
Redirecciones¶
Una característica fundamental del shell es su capacidad de redirigir la entrada y salida de los programas. Esto permite cambiar de dónde un programa lee datos y hacia dónde escribe sus resultados, sin modificar el código del programa.
Salida Estándar y Error Estándar¶
Todo programa en Unix/Linux tiene tres flujos de datos estándar abiertos automáticamente:
Entrada estándar (
stdin
, descriptor 0): De dónde el programa lee datosSalida estándar (
stdout
, descriptor 1): Donde el programa escribe su salida normalError estándar (
stderr
, descriptor 2): Donde el programa escribe mensajes de error
Figure 3:Los tres flujos de datos estándar en un programa C: stdin
, stdout
y stderr
. Por defecto, stdin
lee del teclado mientras que stdout
y stderr
escriben a la pantalla.
En C, estos flujos están disponibles como:
#include <stdio.h>
// stdin - entrada estándar (teclado por defecto)
// stdout - salida estándar (pantalla por defecto)
// stderr - error estándar (pantalla por defecto)
int main(void) {
fprintf(stdout, "Esto es salida normal\n");
fprintf(stderr, "Esto es un mensaje de error\n");
// printf escribe a stdout por defecto
printf("Salida normal\n");
return EXIT_SUCCESS;
}
Redirección de Salida¶
El shell puede redirigir hacia dónde va la salida de un programa usando el operador >
:
# Redirige stdout a un archivo (sobrescribe)
./mi_programa > salida.txt
# Redirige stdout a un archivo (agrega al final)
./mi_programa >> salida.txt
# Redirige stderr a un archivo
./mi_programa 2> errores.txt
# Redirige ambos stdout y stderr al mismo archivo
./mi_programa > todo.txt 2>&1
Figure 4:Distintas formas de redirección en el shell. El programa no necesita modificarse, el shell conecta automáticamente los flujos a archivos.
Desde el punto de vista del programa en C, no necesitás hacer nada especial: simplemente usá printf
(para stdout) y fprintf(stderr, ...)
(para stderr) normalmente. El shell se encarga de la redirección.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
// Esto puede ir a pantalla o a un archivo según el shell lo redirija
printf("Línea 1 de salida\n");
printf("Línea 2 de salida\n");
// Los errores se mantienen separados
fprintf(stderr, "Advertencia: algo ocurrió\n");
return EXIT_SUCCESS;
}
# Ejecuta y guarda solo la salida normal en archivo.txt
./mi_programa > archivo.txt
# Los errores aún aparecen en pantalla
# Para capturar solo los errores
./mi_programa 2> errores.txt
Redirección de Entrada¶
De forma similar, el shell puede cambiar de dónde un programa lee su entrada usando el operador <
:
# Lee stdin desde un archivo en lugar del teclado
./mi_programa < datos.txt
Cualquier lectura que tu programa haga desde stdin
(usando scanf
, fgets
, getchar
, etc.) leerá del archivo especificado:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char linea[256];
// Lee líneas de stdin (puede ser teclado o archivo redirigido)
while (fgets(linea, sizeof(linea), stdin) != NULL) {
printf("Leí: %s", linea);
}
return EXIT_SUCCESS;
}
# Si ejecutás directamente, lee del teclado
./mi_programa
# Con redirección, lee del archivo
./mi_programa < datos.txt
Canalizaciones (Pipes)¶
Las canalizaciones o pipes son uno de los conceptos más poderosos del shell Unix. Permiten conectar la salida de un programa directamente con la entrada de otro, creando cadenas de procesamiento de datos.
El operador |
(pipe) conecta stdout
del primer programa con stdin
del segundo:
# La salida de programa1 se convierte en la entrada de programa2
programa1 | programa2
# Ejemplo real: cuenta las líneas de salida de ls
ls -l | wc -l
Figure 5:Canalizaciones (pipes) en Unix. La salida estándar de un programa se conecta con la entrada estándar del siguiente, permitiendo construir cadenas de procesamiento.
Filosofía Unix: Hacer Una Cosa Bien¶
Las canalizaciones promueven la filosofía Unix: escribir programas pequeños que hagan una cosa muy bien, y combinarlos para tareas complejas. Tu programa C puede ser un eslabón en esta cadena.
Ejemplo: Filtro de Números Pares¶
// filtro_pares.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int numero;
// Lee números de stdin, uno por línea
while (scanf("%d", &numero) == 1) {
// Solo imprime los pares
if (numero % 2 == 0) {
printf("%d\n", numero);
}
}
return EXIT_SUCCESS;
}
Este programa se convierte en un filtro reutilizable:
# Genera números del 1 al 10 y filtra solo los pares
seq 1 10 | ./filtro_pares
# Salida: 2, 4, 6, 8, 10
# Combina con otros programas
seq 1 100 | ./filtro_pares | wc -l # Cuenta cuántos pares hay
Figure 6:Flujo de datos en una canalización que genera números, filtra solo los pares y toma los primeros 5 resultados.
Canalizaciones Complejas¶
Podés encadenar múltiples programas:
# Genera números, filtra pares, suma los primeros 5
seq 1 100 | ./filtro_pares | head -5 | ./sumador
Cada programa en la cadena:
Lee de
stdin
Procesa los datos
Escribe a
stdout
El shell conecta todo automáticamente
Consideraciones de Diseño¶
Para que tu programa funcione bien en canalizaciones:
Lee de
stdin
si no hay archivo especificado: Permite que el programa reciba datos por pipe o por archivoEscribe resultados a
stdout
: La salida normal va astdout
Mensajes de error a
stderr
: No contaminesstdout
con erroresManeja EOF correctamente: Detectá cuando la entrada termina (
fgets
retornaNULL
,scanf
retornaEOF
)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
FILE *entrada = stdin; // Por defecto, stdin
// Si hay un argumento, abre ese archivo
if (argc > 1) {
entrada = fopen(argv[1], "r");
if (entrada == NULL) {
fprintf(stderr, "Error: no se pudo abrir %s\n", argv[1]);
return EXIT_FAILURE;
}
}
char linea[256];
// Lee líneas (de stdin o del archivo)
while (fgets(linea, sizeof(linea), entrada) != NULL) {
// Procesa y escribe a stdout
printf("Procesado: %s", linea);
}
if (entrada != stdin) {
fclose(entrada);
}
return EXIT_SUCCESS;
}
Este diseño permite flexibilidad total:
# Lee del archivo directamente
./mi_programa datos.txt
# Lee de stdin (teclado)
./mi_programa
# Lee de stdin vía redirección
./mi_programa < datos.txt
# Lee de stdin vía canalización
cat datos.txt | ./mi_programa
Ejemplo Completo: Conversor de Temperatura¶
Veamos un ejemplo que integra todos estos conceptos:
// temp_converter.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void mostrar_ayuda(const char *programa) {
fprintf(stderr, "Uso: %s [-c|-f] [archivo]\n", programa);
fprintf(stderr, " -c : Convierte de Fahrenheit a Celsius\n");
fprintf(stderr, " -f : Convierte de Celsius a Fahrenheit\n");
fprintf(stderr, "Si no se especifica archivo, lee de stdin\n");
}
int main(int argc, char *argv[]) {
FILE *entrada = stdin;
char modo = 'c'; // Por defecto: F->C
// Procesa opciones
int archivo_idx = 1;
if (argc > 1 && argv[1][0] == '-') {
if (strcmp(argv[1], "-c") == 0) {
modo = 'c';
archivo_idx = 2;
} else if (strcmp(argv[1], "-f") == 0) {
modo = 'f';
archivo_idx = 2;
} else if (strcmp(argv[1], "-h") == 0) {
mostrar_ayuda(argv[0]);
return EXIT_SUCCESS;
} else {
fprintf(stderr, "Error: opción desconocida %s\n", argv[1]);
mostrar_ayuda(argv[0]);
return EXIT_FAILURE;
}
}
// Abre archivo si se especificó
if (argc > archivo_idx) {
entrada = fopen(argv[archivo_idx], "r");
if (entrada == NULL) {
fprintf(stderr, "Error: no se pudo abrir %s\n", argv[archivo_idx]);
return EXIT_FAILURE;
}
}
// Procesa entrada línea por línea
double temp;
while (fscanf(entrada, "%lf", &temp) == 1) {
double resultado;
if (modo == 'c') {
// Fahrenheit a Celsius
resultado = (temp - 32.0) * 5.0 / 9.0;
printf("%.2f°F = %.2f°C\n", temp, resultado);
} else {
// Celsius a Fahrenheit
resultado = temp * 9.0 / 5.0 + 32.0;
printf("%.2f°C = %.2f°F\n", temp, resultado);
}
}
if (entrada != stdin) {
fclose(entrada);
}
return EXIT_SUCCESS;
}
Este programa puede usarse de múltiples formas:
# Uso interactivo
./temp_converter -c
32
212
# Desde archivo
echo -e "32\n98.6\n212" > temps.txt
./temp_converter -c temps.txt
# Con redirección
./temp_converter -c < temps.txt
# Con canalización
echo "100" | ./temp_converter -f
# Encadenado con otros comandos
seq 0 10 100 | ./temp_converter -f | grep "°C"
La separación entre stdout
(resultados) y stderr
(mensajes de error y ayuda) permite que el programa funcione correctamente en canalizaciones sin contaminar los datos con mensajes no deseados.