Criando uma CLI com getopt e argparse
1. Introdução à Criação de CLIs em C
Interfaces de linha de comando (CLIs) são a porta de entrada para a maioria dos programas escritos em C. Elas permitem que usuários controlem o comportamento do programa sem modificar o código-fonte, passando parâmetros como --verbose, -o arquivo.txt ou --help. Essa flexibilidade é essencial para ferramentas que precisam se integrar a scripts, pipelines ou ambientes de automação.
Os padrões POSIX e GNU estabelecem convenções amplamente adotadas: opções curtas com um único traço (-v) e opções longas com dois traços (--verbose). Implementar manualmente o parsing desses argumentos é tedioso e propenso a erros. Felizmente, a linguagem C oferece bibliotecas robustas para essa tarefa. A biblioteca padrão getopt (e sua extensão getopt_long) está disponível em qualquer sistema POSIX. Já a biblioteca argparse, mais moderna e de cabeçalho único, oferece uma API mais expressiva e recursos como argumentos posicionais, tipos e subcomandos.
Este artigo explora ambas as abordagens, desde os fundamentos do getopt até a construção de uma CLI rica com argparse, com exemplos práticos de código.
2. Fundamentos do getopt Padrão
A função getopt() é a espinha dorsal do parsing de opções curtas em C. Ela é declarada em <unistd.h> e utiliza três variáveis globais:
- optarg: aponta para o argumento de uma opção que requer valor.
- optind: índice do próximo elemento a ser processado em argv.
- optopt: armazena a opção que causou erro.
A string de formato define as opções esperadas. Por exemplo, "a:b::c" significa:
- a: → opção -a com argumento obrigatório.
- b:: → opção -b com argumento opcional (dois pontos).
- c → opção -c sem argumento.
O loop básico é:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "a:b::c")) != -1) {
switch (opt) {
case 'a':
printf("Opção -a com argumento: %s\n", optarg);
break;
case 'b':
printf("Opção -b com argumento: %s\n", optarg ? optarg : "(nenhum)");
break;
case 'c':
printf("Opção -c ativada\n");
break;
case '?':
fprintf(stderr, "Opção desconhecida: -%c\n", optopt);
return 1;
}
}
// Argumentos posicionais restantes estão em argv[optind..argc-1]
for (int i = optind; i < argc; i++)
printf("Argumento posicional: %s\n", argv[i]);
return 0;
}
A string de formato controla rigorosamente o que é aceito. Opções não listadas retornam '?' e optopt indica qual caractere causou o erro.
3. Opções Longas com getopt_long
Para suporte a opções longas como --verbose, usamos getopt_long(), disponível em <getopt.h> em sistemas GNU/Linux. A estrutura option é definida como:
struct option {
const char *name; // Nome longo (ex: "verbose")
int has_arg; // no_argument, required_argument, optional_argument
int *flag; // Se NULL, retorna val; senão, armazena val em *flag
int val; // Valor retornado ou armazenado
};
Exemplo combinando opções curtas e longas:
#include <stdio.h>
#include <getopt.h>
int main(int argc, char *argv[]) {
int verbose = 0;
char *output = NULL;
int help = 0;
struct option long_options[] = {
{"verbose", no_argument, &verbose, 1},
{"output", required_argument, NULL, 'o'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0} // Terminador
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "vo:h", long_options, &option_index)) != -1) {
switch (opt) {
case 0:
// flag foi setada automaticamente
break;
case 'v':
verbose = 1;
break;
case 'o':
output = optarg;
break;
case 'h':
help = 1;
break;
case '?':
return 1;
}
}
if (verbose) printf("Modo verboso ativado\n");
if (output) printf("Arquivo de saída: %s\n", output);
if (help) printf("Ajuda solicitada\n");
return 0;
}
Note que quando flag não é NULL, getopt_long retorna 0 e a variável apontada é atualizada. Isso simplifica o código para opções booleanas.
4. Implementando uma CLI Completa com getopt
Vamos construir uma ferramenta de log que aceita:
- -l ou --level: nível do log (obrigatório)
- -f ou --file: arquivo de saída (opcional)
- -v ou --verbose: modo verboso
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
void usage(const char *prog) {
fprintf(stderr, "Uso: %s -l LEVEL [-f FILE] [-v]\n", prog);
fprintf(stderr, " -l, --level LEVEL Nível do log (DEBUG, INFO, ERROR)\n");
fprintf(stderr, " -f, --file FILE Arquivo de saída (padrão: stdout)\n");
fprintf(stderr, " -v, --verbose Modo verboso\n");
}
int main(int argc, char *argv[]) {
char *level = NULL;
char *file = NULL;
int verbose = 0;
struct option long_opts[] = {
{"level", required_argument, NULL, 'l'},
{"file", required_argument, NULL, 'f'},
{"verbose", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
int opt;
while ((opt = getopt_long(argc, argv, "l:f:vh", long_opts, NULL)) != -1) {
switch (opt) {
case 'l':
level = optarg;
break;
case 'f':
file = optarg;
break;
case 'v':
verbose = 1;
break;
case 'h':
usage(argv[0]);
return 0;
case '?':
usage(argv[0]);
return 1;
}
}
if (!level) {
fprintf(stderr, "Erro: --level é obrigatório\n");
usage(argv[0]);
return 1;
}
if (verbose) printf("Nível: %s, Arquivo: %s\n", level, file ? file : "stdout");
// Lógica principal aqui...
return 0;
}
O tratamento de erros é explícito: opção desconhecida gera '?' e argumento faltante em opção obrigatória também retorna '?'. A função usage() centraliza as mensagens de ajuda.
5. Introdução ao argparse (Biblioteca Moderna)
A biblioteca argparse (disponível em github.com/cofyc/argparse) é um parser de argumentos de linha de comando em C, inspirado no módulo argparse do Python. É um único arquivo .h que você pode copiar para seu projeto.
Instalação: basta baixar argparse.h e incluí-lo no código. Compile com qualquer compilador C99+.
Conceitos fundamentais:
- Argumentos posicionais: valores obrigatórios na ordem especificada.
- Argumentos opcionais: começam com - ou --.
- Flags: opções booleanas que não recebem valor.
- Subcomandos: comandos aninhados (como git commit).
O fluxo básico é:
#include "argparse.h"
int main(int argc, char *argv[]) {
const char *const usage[] = {
"meu_programa [opções] ARQUIVO",
NULL,
};
// Definição dos argumentos
struct argparse_option options[] = {
OPT_HELP(),
OPT_BOOLEAN('v', "verbose", NULL, "modo verboso"),
OPT_STRING('o', "output", NULL, "arquivo de saída"),
OPT_END(),
};
// Estrutura do parser
struct argparse argparse;
argparse_init(&argparse, options, usage, 0);
// Parsing
argparse_parse(&argparse, argc, argv);
// Após o parsing, os argumentos posicionais estão em argv[argc - n...]
return 0;
}
OPT_HELP() adiciona automaticamente -h/--help. As macros OPT_BOOLEAN, OPT_STRING, OPT_INTEGER, OPT_FLOAT definem os tipos.
6. Construindo uma CLI Rica com argparse
Vamos criar uma ferramenta de conversão de arquivos com --input, --output e --format:
#include <stdio.h>
#include <stdlib.h>
#include "argparse.h"
int main(int argc, char *argv[]) {
const char *const usage[] = {
"converter [opções]",
"Converte arquivos entre formatos.",
NULL,
};
char *input = NULL;
char *output = NULL;
char *format = "txt";
int verbose = 0;
int count = 1;
struct argparse_option options[] = {
OPT_HELP(),
OPT_STRING('i', "input", &input, "arquivo de entrada (obrigatório)"),
OPT_STRING('o', "output", &output, "arquivo de saída"),
OPT_STRING('f', "format", &format, "formato de saída (txt, csv, json)"),
OPT_BOOLEAN('v', "verbose", &verbose, "exibir informações detalhadas"),
OPT_INTEGER('c', "count", &count, "número de conversões"),
OPT_END(),
};
struct argparse argparse;
argparse_init(&argparse, options, usage, 0);
argparse_describe(&argparse, "\nFerramenta de conversão de arquivos.", "\nExemplo: converter -i dados.csv -o dados.json -f json -v");
int argc2 = argc;
char **argv2 = argv;
argparse_parse(&argparse, argc2, argv2);
if (!input) {
fprintf(stderr, "Erro: --input é obrigatório\n");
argparse_usage(&argparse);
return 1;
}
if (verbose) {
printf("Entrada: %s\n", input);
printf("Saída: %s\n", output ? output : "(automático)");
printf("Formato: %s\n", format);
printf("Contagem: %d\n", count);
}
// Lógica de conversão...
return 0;
}
A função argparse_describe adiciona descrições extras ao help. OPT_STRING armazena diretamente em um ponteiro char*, e OPT_INTEGER em um int. O valor padrão é definido pela inicialização das variáveis.
7. Tratamento Avançado e Boas Práticas
Mensagens de erro amigáveis são cruciais. Com getopt, implemente uma função usage() que exiba a sintaxe e as opções. Com argparse, use argparse_usage() após detectar erro.
Validação de argumentos pode ser feita após o parsing. Por exemplo, verificar se o formato é um dos valores permitidos:
if (strcmp(format, "txt") != 0 && strcmp(format, "csv") != 0 && strcmp(format, "json") != 0) {
fprintf(stderr, "Erro: formato '%s' inválido. Use txt, csv ou json.\n", format);
return 1;
}
Para compatibilidade entre as bibliotecas, você pode usar getopt para opções curtas básicas e argparse para funcionalidades mais complexas, desde que não haja conflito de nomes. Em projetos que exigem portabilidade estrita (POSIX), prefira getopt. Para aplicações modernas com muitos argumentos, argparse reduz significativamente o código boilerplate.
8. Comparação e Escolha da Abordagem
| Característica | getopt |
argparse |
|---|---|---|
| Dependências | Nenhuma (padrão POSIX) | Cabeçalho único (C99+) |
| Opções longas | getopt_long (GNU) |
Nativo |
| Tipos de dados | Apenas string | string, int, float, booleano |
| Argumentos posicionais | Manual | Nativo |
| Subcomandos | Não | Sim |
| Mensagens de erro | Manual | Automáticas |
| Tamanho do código | Maior (mais controle) | Menor (mais abstração) |
Use getopt quando:
- O projeto precisa ser estritamente POSIX.
- O número de opções é pequeno (menos de 5).
- Você quer controle total sobre o parsing.
Use argparse quando:
- A CLI tem muitos argumentos ou subcomandos.
- Você precisa de validação de tipos automática.
- A produtividade é mais importante que o tamanho binário.
Ambas as bibliotecas são ferramentas valiosas no arsenal do programador C. A escolha depende do contexto do projeto e da complexidade da interface desejada.
Referências
- GNU C Library: Getopt — Documentação oficial da implementação GNU de
getoptegetopt_long, com todos os detalhes da API. - POSIX getopt specification — Especificação oficial POSIX para a função
getopt, referência para portabilidade. - argparse: A command line arguments parser in C — Repositório oficial da biblioteca
argparsede cabeçalho único, com exemplos e documentação. - Beej's Guide to C Programming: Command-line arguments — Tutorial prático sobre parsing de argumentos em C, cobrindo
getopte técnicas manuais. - Using getopt in C: A Complete Guide — Guia da IBM sobre o uso de
getoptem sistemas mainframe, com exemplos detalhados e tratamento de erros.