Argumentos de linha de comando
Nesta aula, você aprenderá a trabalhar com argumentos de linha de comando em C, entendendo os parâmetros argc e argv da função main, como fazer parsing manual, e uma visão geral da biblioteca getopt. Ao final, será capaz de criar programas que recebem e processam entradas do usuário via terminal.
Quando você executa um programa em um terminal, muitas vezes ele aceita opções e parâmetros que modificam seu comportamento, como ls -l ou grep -i padrão arquivo.txt. Em C, esses argumentos são passados para a função main através de dois parâmetros: argc (argument count) e argv (argument vector). Dominar esse mecanismo é essencial para criar ferramentas de linha de comando robustas e flexíveis, desde simples utilitários até programas complexos com muitas opções.
Nesta aula, vamos explorar a fundo como esses parâmetros funcionam, como acessá-los e processá-los, e como utilizar a biblioteca getopt para simplificar o parsing de opções. Você verá exemplos práticos que vão desde a impressão de argumentos até a implementação de um programa com múltiplas opções.
argc e argv
A função main pode ser declarada de duas formas: sem parâmetros (int main(void)) ou com os parâmetros argc e argv. Quando você usa a segunda forma, o sistema operacional fornece automaticamente os argumentos digitados na linha de comando.
argc é um inteiro que indica quantos argumentos foram passados, incluindo o nome do próprio programa. argv é um array de strings (ponteiros para char) onde cada elemento é um argumento. O primeiro elemento, argv[0], é o nome do programa como foi executado. Os argumentos seguintes, argv[1] até argv[argc-1], são os parâmetros fornecidos pelo usuário. Por convenção, argv[argc] é um ponteiro nulo (NULL), o que pode ser útil para percorrer o array sem usar argc.
Vejamos um exemplo simples que imprime todos os argumentos:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Número de argumentos: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
Se você compilar e executar esse programa com, por exemplo, ./programa um dois tres, a saída será:
Número de argumentos: 4
argv[0] = ./programa
argv[1] = um
argv[2] = dois
argv[3] = tres
Note que os argumentos são separados por espaços. Se você precisar passar um argumento que contenha espaços, deve envolvê-lo em aspas duplas ou escapar os espaços no terminal. Por exemplo, ./programa "hello world" passará hello world como um único argumento.
Parsing manual
Muitas vezes, precisamos interpretar os argumentos de acordo com regras específicas, como reconhecer opções que começam com - ou --. O parsing manual envolve percorrer argv e decidir o que fazer com cada argumento. Isso dá controle total, mas exige cuidado para lidar com todos os casos.
Um padrão comum é processar as opções primeiro e depois os argumentos restantes. Por exemplo, vamos criar um programa que aceita uma opção -v (verbose) e um nome de arquivo. O parsing manual poderia ser assim:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int verbose = 0;
char *filename = NULL;
// Percorre todos os argumentos a partir do índice 1
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
verbose = 1;
} else if (argv[i][0] == '-') {
printf("Opção desconhecida: %s\n", argv[i]);
} else {
// Se ainda não temos filename, assume que é o arquivo
if (filename == NULL) {
filename = argv[i];
} else {
printf("Argumento extra ignorado: %s\n", argv[i]);
}
}
}
if (verbose) {
printf("Modo verboso ativado.\n");
}
if (filename) {
printf("Arquivo: %s\n", filename);
} else {
printf("Nenhum arquivo especificado.\n");
}
return 0;
}
Esse código é simples, mas pode ficar complexo quando há muitas opções, especialmente se algumas aceitam valores (como -o arquivo). Para opções com valor, você precisa verificar se o próximo argumento existe e consumi-lo. Por exemplo:
if (strcmp(argv[i], "-o") == 0) {
if (i + 1 < argc) {
output = argv[++i];
} else {
printf("Erro: -o requer um valor.\n");
return 1;
}
}
Essa abordagem é funcional, mas reinventa a roda. Para programas com muitas opções, é melhor usar uma biblioteca como getopt.
getopt (visão geral)
A função getopt é uma função padrão da biblioteca POSIX (disponível em sistemas Unix-like, incluindo Linux e macOS) que simplifica o parsing de opções de linha de comando. Ela processa automaticamente opções curtas (como -a, -b) e permite que algumas opções recebam valores (como -o valor).
Para usar getopt, você precisa incluir <unistd.h> e chamá-la em um loop. O protótipo é:
int getopt(int argc, char * const argv[], const char *optstring);
O parâmetro optstring é uma string que especifica quais opções são válidas. Se uma opção espera um valor, você coloca dois pontos (:) após a letra. Por exemplo, "vo:" indica que -v não recebe valor, mas -o recebe. A função retorna a letra da opção encontrada, ou -1 quando todas as opções foram processadas. Variáveis globais como optarg (valor da opção), optind (índice do próximo argumento) e opterr (controle de mensagens de erro) são usadas.
Um exemplo típico:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int verbose = 0;
char *output = NULL;
int opt;
// o: indica que -o espera um argumento
while ((opt = getopt(argc, argv, "vo:")) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'o':
output = optarg;
break;
default: // '?' para opção desconhecida
fprintf(stderr, "Uso: %s [-v] [-o arquivo]\n", argv[0]);
return 1;
}
}
// Após o loop, optind aponta para o primeiro argumento não-opção
for (int i = optind; i < argc; i++) {
printf("Argumento: %s\n", argv[i]);
}
if (verbose) printf("Verbose ativado\n");
if (output) printf("Saída: %s\n", output);
return 0;
}
Observe que getopt reorganiza argv de forma que os argumentos não-opção fiquem no final, e optind indica onde eles começam. Isso facilita o processamento subsequente.
Exemplos
Vamos consolidar com um exemplo mais completo que combina parsing manual e getopt. Vamos criar um programa que aceita opções -v (verbose), -n (número de repetições) e um texto opcional. Usaremos getopt para as opções e depois processaremos os argumentos restantes.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int verbose = 0;
int repeat = 1;
int opt;
// n: requer argumento; v: não requer
while ((opt = getopt(argc, argv, "vn:")) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'n':
repeat = atoi(optarg);
if (repeat < 1) {
fprintf(stderr, "Erro: -n deve ser positivo.\n");
return 1;
}
break;
default:
fprintf(stderr, "Uso: %s [-v] [-n vezes] [mensagem]\n", argv[0]);
return 1;
}
}
// Coleta a mensagem dos argumentos restantes
char *message = NULL;
if (optind < argc) {
message = argv[optind];
} else {
message = "Olá, mundo!";
}
// Imprime a mensagem 'repeat' vezes, com verbosidade
for (int i = 0; i < repeat; i++) {
if (verbose) printf("[%d] ", i + 1);
printf("%s\n", message);
}
return 0;
}
Teste com diferentes combinações:
$ ./programa -v -n 3 "Teste"
[1] Teste
[2] Teste
[3] Teste
$ ./programa -n 2
Olá, mundo!
Olá, mundo!
Esse exemplo mostra como getopt lida com opções que têm valores e como separar as opções dos argumentos posicionais.
Boas práticas e observações finais
Ao trabalhar com argumentos de linha de comando, siga estas recomendações:
- Sempre valide a entrada: verifique se opções que exigem valor realmente receberam um, e se valores numéricos são válidos.
- Forneça uma mensagem de uso (usage) clara, indicando as opções e argumentos esperados.
- Prefira
getoptpara opções curtas e consideregetopt_longpara opções longas (como--verbose). - Documente as opções no código, para que outros programadores entendam a interface.
- Lembre-se de que
argvé modificado porgetopt; se precisar preservar os argumentos originais, faça uma cópia antes.
Dominar argumentos de linha de comando é um passo importante para criar ferramentas de terminal profissionais. Com as técnicas desta aula, você pode construir programas flexíveis e fáceis de usar.
Referências
- cppreference: main function
- getopt(3) - Linux manual page
- POSIX getopt specification
- GNU C Library: Getopt
- Wikipedia: getopt
- IBM Documentation: getopt()
Exercícios
- Escreva um programa que imprima o número de argumentos e cada argumento em uma linha, sem usar
argc(use a condiçãoargv[i] != NULL). - Crie um programa que receba dois números via linha de comando e imprima a soma. Trate o caso de argumentos insuficientes ou não numéricos.
- Escreva um programa que use
getoptpara aceitar as opções-a(ativa algo) e-b valor(define um valor). Imprima o que foi configurado. - Modifique o exemplo da aula para que a opção
-naceite um valor negativo e imprima uma mensagem de erro clara, retornando um código de erro. - Pesquise sobre
getopt_long(para opções longas) e escreva um exemplo que use uma opção longa--verbosee uma curta-vcomo sinônimos.
#include <stdio.h>
int main(int argc, char *argv[]) {
int i = 0;
while (argv[i] != NULL) {
printf("argv[%d] = %s\n", i, argv[i]);
i++;
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Uso: %s num1 num2\n", argv[0]);
return 1;
}
char *endptr;
double a = strtod(argv[1], &endptr);
if (*endptr != '\0') {
fprintf(stderr, "Erro: '%s' não é um número válido.\n", argv[1]);
return 1;
}
double b = strtod(argv[2], &endptr);
if (*endptr != '\0') {
fprintf(stderr, "Erro: '%s' não é um número válido.\n", argv[2]);
return 1;
}
printf("Soma: %f\n", a + b);
return 0;
}
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int a_flag = 0;
char *b_value = NULL;
int opt;
while ((opt = getopt(argc, argv, "ab:")) != -1) {
switch (opt) {
case 'a':
a_flag = 1;
break;
case 'b':
b_value = optarg;
break;
default:
fprintf(stderr, "Uso: %s [-a] [-b valor]\n", argv[0]);
return 1;
}
}
printf("a_flag: %d\n", a_flag);
printf("b_value: %s\n", b_value ? b_value : "(não definido)");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int verbose = 0;
int repeat = 1;
int opt;
while ((opt = getopt(argc, argv, "vn:")) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
case 'n':
repeat = atoi(optarg);
if (repeat <= 0) {
fprintf(stderr, "Erro: -n deve ser um inteiro positivo.\n");
return 1;
}
break;
default:
fprintf(stderr, "Uso: %s [-v] [-n vezes] [mensagem]\n", argv[0]);
return 1;
}
}
char *message = (optind < argc) ? argv[optind] : "Olá, mundo!";
for (int i = 0; i < repeat; i++) {
if (verbose) printf("[%d] ", i + 1);
printf("%s\n", message);
}
return 0;
}
#include <stdio.h>
#include <getopt.h>
int main(int argc, char *argv[]) {
int verbose = 0;
int opt;
static struct option long_options[] = {
{"verbose", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
while ((opt = getopt_long(argc, argv, "v", long_options, NULL)) != -1) {
switch (opt) {
case 'v':
verbose = 1;
break;
default:
fprintf(stderr, "Uso: %s [--verbose]\n", argv[0]);
return 1;
}
}
if (verbose) printf("Verbose ativado\n");
return 0;
}