Los argumentos de la línea de órdenes en C++: cómo llegan a main, cómo pasarlos a un std::vector<std::string_view>, un analizador pequeño con opciones cortas, largas, --help y --, cómo convertir números sin sorpresas con std::from_chars y qué código de salida devolver en cada caso.
También te puede interesar
argc, argv y un vector de string_view
Los argumentos llegan a main igual que en C: argc dice cuántos hay y argv es un array de cadenas al estilo C. El primero, argv[0], es el nombre con el que se lanzó el programa. En C++ moderno lo más cómodo es meterlos en un std::vector<std::string_view> y olvidarse de los punteros: string_view no copia nada, solo mira las cadenas que ya existen, que viven hasta que acaba el programa.
#include <print>
#include <string_view>
#include <vector>
int main(int argc, char* argv[])
{
const std::vector<std::string_view> args(argv + 1, argv + argc); // sin argv[0]
std::println("programa: {}", argv[0]);
std::println("{} argumentos:", args.size());
for (std::size_t i = 0; i < args.size(); ++i)
std::println(" [{}] «{}»", i, args[i]);
}./args hola "dos palabras" --rapido -n 5
salidaprograma: ./args 5 argumentos: [0] «hola» [1] «dos palabras» [2] «--rapido» [3] «-n» [4] «5»
Las comillas las quita el intérprete de órdenes antes de llamar al programa: "dos palabras" llega como un solo argumento, sin comillas. El programa no ve nada de eso, solo recibe cinco cadenas.
Un analizador de opciones pequeño, sin bibliotecas
Para un programa con unas pocas opciones no hace falta ninguna biblioteca. Este, saludo, acepta las formas habituales en Linux: opciones cortas (-n 3), largas (--veces=3), --help, el separador -- y nombres sueltos. Lo importante está en los detalles: valida los números, escribe los errores en stderr y termina con un código de salida que dice qué pasó.
#include <cctype>
#include <charconv>
#include <cstdlib>
#include <format>
#include <optional>
#include <print>
#include <string>
#include <string_view>
#include <vector>
struct Opciones {
int veces = 1;
bool mayusculas = false;
std::vector<std::string_view> nombres;
};
constexpr int USO_INCORRECTO = 2; // la convención de las herramientas de GNU
void ayuda(std::string_view prog)
{
std::println("Uso: {} [opciones] NOMBRE...", prog);
std::println("Saluda a cada NOMBRE.");
std::println("");
std::println(" -n, --veces=N repite el saludo N veces (de 1 a 10; por omisión, 1)");
std::println(" -m, --mayusculas escribe el saludo en mayúsculas");
std::println(" -h, --help muestra esta ayuda y sale");
}
std::optional<int> entero(std::string_view s)
{
int n{};
auto [fin, err] = std::from_chars(s.data(), s.data() + s.size(), n);
if (err != std::errc{} || fin != s.data() + s.size()) return std::nullopt;
return n;
}
[[noreturn]] void fallo(std::string_view prog, std::string_view mensaje)
{
std::println(stderr, "{}: {}", prog, mensaje);
std::println(stderr, "Prueba '{} --help' para más información.", prog);
std::exit(USO_INCORRECTO);
}
int main(int argc, char* argv[])
{
const std::vector<std::string_view> args(argv + 1, argv + argc);
const std::string_view prog = argv[0];
Opciones op;
for (std::size_t i = 0; i < args.size(); ++i) {
const std::string_view a = args[i];
if (a == "-h" || a == "--help") {
ayuda(prog);
return EXIT_SUCCESS;
} else if (a == "-m" || a == "--mayusculas") {
op.mayusculas = true;
} else if (a == "-n" || a.starts_with("--veces=")) {
std::string_view valor;
if (a == "-n") {
if (i + 1 == args.size()) fallo(prog, "a la opción -n le falta el número");
valor = args[++i];
} else {
valor = a.substr(a.find('=') + 1);
}
auto n = entero(valor);
if (!n || *n < 1 || *n > 10) fallo(prog, std::format("número de veces no válido: '{}'", valor));
op.veces = *n;
} else if (a == "--") { // lo que sigue son nombres, aunque empiecen por -
op.nombres.insert(op.nombres.end(), args.begin() + i + 1, args.end());
break;
} else if (a.size() > 1 && a.starts_with('-')) {
fallo(prog, std::format("opción desconocida: {}", a));
} else {
op.nombres.push_back(a);
}
}
if (op.nombres.empty()) fallo(prog, "falta al menos un NOMBRE");
for (auto nombre : op.nombres) {
std::string s = std::format("¡Hola, {}!", nombre);
if (op.mayusculas)
for (char& c : s) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
for (int i = 0; i < op.veces; ++i) std::println("{}", s);
}
return EXIT_SUCCESS;
}Así responde a distintas órdenes. Primero, un uso normal y la ayuda:
./saludo -n 2 Ana Luis
salida¡Hola, Ana! ¡Hola, Ana! ¡Hola, Luis! ¡Hola, Luis!
./saludo --help
salidaUso: ./saludo [opciones] NOMBRE... Saluda a cada NOMBRE. -n, --veces=N repite el saludo N veces (de 1 a 10; por omisión, 1) -m, --mayusculas escribe el saludo en mayúsculas -h, --help muestra esta ayuda y sale
./saludo --mayusculas --veces=1 Ana
salida¡HOLA, ANA!
std::toupper solo cambia las letras sin tilde: los bytes de «¡» en UTF-8 los deja tal cual, que es lo correcto aquí. Con -- se pueden pasar nombres que empiezan por guion sin que se tomen por opciones:
./saludo -- -Zoe
salida¡Hola, -Zoe!
Y los errores. Cada uno sale por stderr, sugiere la ayuda y termina con el código 2:
./saludo --colores Ana
salida con error, código 2./saludo: opción desconocida: --colores Prueba './saludo --help' para más información.
./saludo -n 99 Ana
salida con error, código 2./saludo: número de veces no válido: '99' Prueba './saludo --help' para más información.
./saludo -m
salida con error, código 2./saludo: falta al menos un NOMBRE Prueba './saludo --help' para más información.
El mensaje empieza por el nombre del programa, como hacen las herramientas de Linux, para que se sepa quién se queja cuando el programa forma parte de un script más grande.
Convertir texto a número sin sorpresas: std::from_chars
El analizador usa std::from_chars (C++17) para leer el número. Las alternativas heredadas de C tienen problemas: atoi devuelve 0 si el texto no es un número, así que no se distingue un error de un 0 de verdad, y su resultado es indefinido si el número no cabe. std::stoi lanza excepciones y acepta basura al final («12abc» le parece 12). std::from_chars no lanza, no depende del idioma del sistema y dice exactamente qué pasó:
#include <charconv>
#include <format>
#include <print>
#include <string>
#include <string_view>
#include <system_error>
void probar(std::string_view s)
{
int n = -1;
auto [fin, err] = std::from_chars(s.data(), s.data() + s.size(), n);
std::string texto = std::format("«{}»", s);
if (err == std::errc::invalid_argument)
std::println("{:<15} no es un número", texto);
else if (err == std::errc::result_out_of_range)
std::println("{:<15} no cabe en un int", texto);
else if (fin != s.data() + s.size())
std::println("{:<15} número {} seguido de basura: «{}»", texto, n, std::string_view(fin, s.data() + s.size()));
else
std::println("{:<15} bien: {}", texto, n);
}
int main()
{
for (auto s : {"42", "-7", "abc", "12abc", " 3", "99999999999", ""})
probar(s);
}salida«42» bien: 42 «-7» bien: -7 «abc» no es un número «12abc» número 12 seguido de basura: «abc» « 3» no es un número «99999999999» no cabe en un int «» no es un número
Fíjate en " 3", con un espacio delante: from_chars no se salta espacios, a diferencia de atoi o std::stoi. Si quieres aceptarlos, quítalos antes a propósito.
Los códigos de salida
El valor que devuelve main (o el que se pasa a std::exit) es el código de salida, y es la forma en que un script sabe si el programa funcionó. En Bash se consulta con $? o se usa directamente en un if:
./saludo --colores Ana echo $? # 2 if ./saludo -n 1 Ana > /dev/null; then echo "funcionó"; fi
- CódigoSignificado habitual
- 0 (
EXIT_SUCCESS)Todo fue bien - 1 (
EXIT_FAILURE)Error general: no se pudo leer un archivo, falló la red… - 2Uso incorrecto: opción desconocida o argumento no válido (convención de GNU y de Bash)
- 126 y 127Los usa el intérprete: el programa no se puede ejecutar o no existe
- 128 + nEl programa murió por la señal n (130 = Ctrl+C, 139 = violación de segmento)
Solo hay 256 códigos (de 0 a 255): return 256 llega al intérprete como 0, así que nunca devuelvas un número de errores como código de salida.
¿Y para programas más grandes?
Cuando las opciones crecen (subórdenes, opciones repetidas, valores por omisión documentados, autocompletado), compensa una biblioteca. En C++ las más usadas son CLI11 y cxxopts, las dos de un solo archivo de cabecera, y Boost.Program_options. En sistemas POSIX también está getopt_long, la función de C que usan casi todas las herramientas de GNU.
Todo el código de esta página se compiló con GCC 15.3 (C++23, -std=c++23 -Wall -Wextra, sin avisos) y se ejecutó en Linux, en un contenedor limpio sin acceso a la red, antes de publicarla; las salidas están copiadas de esa ejecución.