C Argumentos da Linha de Comando

Quando você digita gcc ola.c -o ola, o gcc fica sabendo, sem perguntar nada, qual arquivo compilar e qual nome dar ao programa. Essas palavras depois do nome do programa são os argumentos da linha de comando. Seus programas também podem recebê-los: em vez de pedir "Digite um número:" com scanf, a Ana vai poder escrever ./tabuada 7 e pronto. Nesta aula você vai conhecer a outra forma do main, int main(int argc, char *argv[]), transformar argumentos em números, escrever a mensagem de uso que todo programa de terminal tem e entender o que o shell faz com aspas e asteriscos antes de o seu programa ver qualquer coisa.

Você vai aprender
  • O que são argc e argv e o que há em cada posição de argv.
  • Por que argv[0] é o nome do próprio programa.
  • Como converter um argumento em número com strtol (e por que o atoi engana).
  • Como escrever uma mensagem de uso quando faltam argumentos.
  • O que o shell faz com espaços, aspas, $ e * antes de chamar o programa.
  • Como passar argumentos no editor do guia (o campo Argumentos e o data-args).

Você vai precisar de Arrays, Strings e, para a parte de números, Texto ↔ Número. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com gcc -std=c23 -Wall -Wextra arquivo.c -o programa.


A ideia: o pedido escrito no balcão

Numa padaria, a atendente pode perguntar "O que vai ser?" e esperar você responder (isso é o scanf: o programa pergunta depois de começar). Ou você pode chegar com um papel escrito: "6 pães, 1 leite". A atendente lê o papel e já começa a trabalhar. Os argumentos da linha de comando são o papel: vão junto com o pedido para o programa rodar.

Vantagens do papel: dá para repetir o mesmo pedido sem digitar de novo (a seta para cima do terminal), dá para colocar o comando num script e dá para o programa rodar sem ninguém olhando. É assim que quase todo comando do Linux funciona: ls -l, cp a.txt b.txt, gcc ola.c.


1. A outra forma do main

Até aqui o guia usou int main(void): "o main não recebe nada". Para receber os argumentos, o main ganha dois parâmetros:

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = \"%s\"\n", i, argv[i]);
    }
    return 0;
}
int argc
"argument count", quantos argumentos chegaram, contando o nome do programa. Nunca é menor que 1 num sistema normal.
char *argv[]
"argument vector", a lista dos argumentos: um array de strings (cada char * aponta para um texto terminado em '\0'; veja Ponteiros e Strings).
argv[0]
O nome com que o programa foi chamado.
argv[1] … argv[argc - 1]
Os argumentos que vieram depois, na ordem em que foram digitados.
for (int i = 0; i < argc; i++)
O laço de sempre para percorrer um array, com argc como tamanho.
$ gcc -std=c23 -Wall -Wextra args.c -o args
$ ./args
argc = 1
argv[0] = "./args"
$ ./args Ana 29
argc = 3
argv[0] = "./args"
argv[1] = "Ana"
argv[2] = "29"

Sem argumentos, argc vale 1: só o nome do programa. Com Ana 29, vale 3. E repare: o 29 chegou como o texto "29", e não como número. Todo argumento é uma string; a seção 3 mostra como converter.

Posiçãoargv[0]argv[1]argv[2]argv[3]
Conteúdo para ./args Ana 29"./args""Ana""29"NULL
Depois do último argumento, argv[argc] é sempre NULL (o padrão C garante).
Nomes e formas. argc e argv são só costume: o C aceitaria int main(int n, char *lista[]), mas todo programador reconhece os nomes clássicos, então use-os. char *argv[] e char **argv são a mesma coisa num parâmetro (aula Ponteiro para Ponteiro). Só existem essas duas formas padrão do main: sem parâmetros (void) e com argc e argv.

Experimente: no editor, os argumentos vão no campo Argumentos da caixa. Esta caixa já começa com Ana 29 "São Paulo". Rode e conte: quantos argumentos chegaram? Depois apague as aspas de "São Paulo" e rode de novo. Por fim, deixe o campo vazio.

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = \"%s\"\n", i, argv[i]);
    }
    return 0;
}

2. Mensagem de uso e argv[0]

Um programa que precisa de argumentos deve conferir argc antes de tocar em argv[1]. Se faltar alguma coisa, o costume dos programas de terminal é mostrar uma mensagem de uso (em inglês, usage), que ensina como chamar o programa, e terminar com código de erro:

#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Uso: %s NOME\n", argv[0]);
        return 1;
    }
    printf("Olá, %s!\n", argv[1]);
    return 0;
}
if (argc != 2)
O programa quer exatamente um argumento: o nome do programa e mais um, total 2.
fprintf(stderr, …)
A mensagem de uso é um aviso de erro, então vai para a saída de erros (aula stdin, stdout e stderr).
"Uso: %s NOME\n", argv[0]
Mostra o nome como o usuário digitou. Se ele renomear o executável, a mensagem acompanha. NOME em maiúsculas é a convenção para "troque por um valor seu".
return 1;
Código de saída diferente de 0: falhou (aula exit e atexit).
$ gcc -std=c23 -Wall -Wextra saudar.c -o saudar
$ ./saudar
Uso: ./saudar NOME
$ ./saudar Ana
Olá, Ana!
$ /home/ana/c/saudar
Uso: /home/ana/c/saudar NOME             « argv[0] é o que foi digitado

Com o caminho completo, o argv[0] virou o caminho completo. Por isso ele serve para mensagens, mas não para descobrir onde o executável está: o padrão C nem garante o que vem nele (pode até ser uma string vazia). No Linux e no editor do guia, vem o que foi digitado.


3. Converter um argumento em número

Argumento é texto. Para fazer conta, converta com strtol, conferindo os três jeitos de dar errado (aulas Texto ↔ Número e errno):

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Uso: %s NÚMERO\n", argv[0]);
        return 1;
    }

    char *fim;
    errno = 0;
    long n = strtol(argv[1], &fim, 10);
    if (fim == argv[1] || *fim != '\0' || errno == ERANGE) {
        fprintf(stderr, "%s: \"%s\" não é um número inteiro\n", argv[0], argv[1]);
        return 1;
    }

    for (int i = 1; i <= 10; i++) {
        printf("%ld x %2d = %ld\n", n, i, n * i);
    }
    return 0;
}
TestePega o caso
fim == argv[1]Nenhum algarismo: sete, ou uma string vazia
*fim != '\0'Sobrou texto depois do número: 7x, 7.5
errno == ERANGENúmero grande demais para um long
$ gcc -std=c23 -Wall -Wextra tabuada.c -o tabuada
$ ./tabuada 7
7 x  1 = 7
7 x  2 = 14
7 x  3 = 21
7 x  4 = 28
7 x  5 = 35
7 x  6 = 42
7 x  7 = 49
7 x  8 = 56
7 x  9 = 63
7 x 10 = 70
$ ./tabuada sete
./tabuada: "sete" não é um número inteiro
$ ./tabuada 7x
./tabuada: "7x" não é um número inteiro
$ ./tabuada
Uso: ./tabuada NÚMERO

E o atoi?

Você vai ver muito código com atoi(argv[1]) ("ASCII to integer"). Ele é mais curto, mas não tem como avisar que o texto não era número: devolve 0 e segue em frente.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Uso: %s NÚMERO\n", argv[0]);
        return 1;
    }
    int n = atoi(argv[1]);
    printf("O dobro de %d é %d\n", n, 2 * n);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra atoi.c -o atoi
$ ./atoi 21
O dobro de 21 é 42
$ ./atoi sete
O dobro de 0 é 0                « nenhum aviso: "sete" virou 0

Para o programa, "sete" e "0" são iguais. Em programas de verdade, prefira strtol (inteiros) e strtod (números com vírgula).


4. O shell mexe nos argumentos antes do programa

Quem separa a linha digitada em argumentos não é o seu programa: é o shell (o Bash, no terminal da Ana). Quando o main começa, o trabalho já foi feito. Por isso, o mesmo programa args recebe coisas bem diferentes conforme você escreve:

$ ./args "Ana Maria" 29
argc = 3
argv[0] = "./args"
argv[1] = "Ana Maria"
argv[2] = "29"
$ ./args a\ b
argc = 2
argv[0] = "./args"
argv[1] = "a b"
$ ./args 'preço: $5' "$HOME"
argc = 3
argv[0] = "./args"
argv[1] = "preço: $5"
argv[2] = "/home/ana"
Você digitaO programa recebePor quê
Ana Mariadois argumentosO espaço separa argumentos
"Ana Maria" ou a\ bum argumento, com espaçoAspas (ou a barra invertida) juntam
'preço: $5'preço: $5Aspas simples: nada dentro é interpretado
"$HOME"/home/anaAspas duplas deixam o shell trocar variáveis (aula Variáveis de Ambiente)
*.jpga lista de arquivos .jpgO shell expande o * antes (veja abaixo)

As aspas não chegam ao programa: elas são instruções para o shell. E o asterisco é o caso mais surpreendente. Na pasta fotos da Ana há três imagens:

$ ../args *.jpg
argc = 4
argv[0] = "../args"
argv[1] = "bolo.jpg"
argv[2] = "gato.jpg"
argv[3] = "praia.jpg"
$ ../args "*.jpg"
argc = 2
argv[0] = "../args"
argv[1] = "*.jpg"
$ ../args *.png
argc = 2
argv[0] = "../args"
argv[1] = "*.png"                « nenhum .png: o Bash passa o texto como está

O programa nunca viu o * na primeira chamada: o Bash trocou *.jpg pelos nomes dos arquivos, em ordem alfabética. É por isso que rm *.txt funciona sem que o rm saiba o que é asterisco. (O Guia Bash explica isso em detalhes; aqui basta saber que o seu programa recebe a lista pronta.)

No editor do guia, o campo Argumentos entende espaços, aspas simples, aspas duplas e a barra invertida como o Bash, mas não troca $HOME nem expande *: não há um shell de verdade por trás. Nas páginas do guia, o valor inicial do campo vem do atributo data-args da caixa.

Experimente: esta caixa começa com o campo Argumentos vazio. Rode e veja a mensagem de uso e o código de saída 1. Depois escreva Ana no campo e rode; em seguida Ana Maria (sem aspas) e "Ana Maria" (com aspas). Qual delas recebe a saudação?

#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Uso: %s NOME\n", argv[0]);
        return 1;
    }
    printf("Olá, %s!\n", argv[1]);
    return 0;
}

Formas diferentes, e qual usar

FormaQuando usar
int main(void)O programa não usa argumentos
int main(int argc, char *argv[])A forma mais comum quando usa (a deste guia)
int main(int argc, char **argv)Exatamente igual; aparece muito em código antigo e em projetos grandes
int main([[maybe_unused]] int argc, char **argv) C23Usa só o argv e quer calar o aviso unused parameter
for (char **p = argv; *p != NULL; p++)Percorrer sem argc, aproveitando o NULL do fim

E, entre perguntar com scanf/fgets e receber por argumento: argumentos são melhores para o que o usuário já sabe ao chamar o programa (um nome de arquivo, uma opção, um número) e para o que vai rodar em scripts; perguntar é melhor para conversas (um jogo, um menu). Muitos programas fazem os dois: usam o argumento se ele veio e perguntam se não veio.


Quando dá errado

Falha de segmentação (imagem do núcleo gravada) quando roda sem argumentos
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int n = atoi(argv[1]);
    printf("Você digitou %d\n", n);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra semteste.c -o semteste
semteste.c: In function ‘main’:
semteste.c:4:14: warning: unused parameter ‘argc’ [-Wunused-parameter]
    4 | int main(int argc, char *argv[]) {
      |          ~~~~^~~~
$ ./semteste 5
Você digitou 5
$ ./semteste
Falha de segmentação     (imagem do núcleo gravada) ./semteste

O que significa: sem argumentos, argc é 1 e argv[1] é o NULL do fim da lista. O atoi tentou ler um texto no endereço nulo. O próprio aviso deu a pista: "o parâmetro argc não foi usado", ou seja, ninguém conferiu quantos argumentos chegaram. No editor do guia, ler o endereço nulo nem sempre derruba o programa: este aqui mostra Você digitou 0, o que esconde o erro ainda mais.

Como consertar: confira argc antes de usar qualquer argv[i], com uma mensagem de uso.

"Uso: ./saudar NOME" mesmo passando o nome
$ ./saudar Ana Maria
Uso: ./saudar NOME
$ ./saudar "Ana Maria"
Olá, Ana Maria!

O que significa: sem aspas, o shell separou em dois argumentos e argc virou 3.

Como consertar: para o usuário, use aspas em valores com espaço. Para o programador, decida se faz sentido aceitar vários argumentos (e juntá-los) ou exigir as aspas.

warning: second argument of ‘main’ should be ‘char **’ [-Wmain]
$ gcc -std=c23 -Wall -Wextra tipo.c -o tipo
tipo.c:3:5: warning: second argument of ‘main’ should be ‘char **’ [-Wmain]
    3 | int main(int argc, char argv[]) {
      |     ^~~~
tipo.c: In function ‘main’:
tipo.c:3:25: warning: unused parameter ‘argv’ [-Wunused-parameter]
    3 | int main(int argc, char argv[]) {
      |                    ~~~~~^~~~~~

O que significa: faltou o *: char argv[] seria um texto, e não uma lista de textos.

Como consertar: char *argv[] (ou char **argv). Os tipos dos dois parâmetros do main são fixos.

O número veio 0 (ou errado) sem nenhum aviso

O que significa: o programa usou atoi, que transforma qualquer coisa que não começa com algarismo em 0 (./atoi sete deu "O dobro de 0 é 0"), e "12abc" em 12.

Como consertar: use strtol com as três conferências da seção 3.

Comparei o argumento com == e nunca dá igual
$ gcc -std=c23 -Wall -Wextra opcao.c -o opcao
opcao.c: In function ‘main’:
opcao.c:4:29: warning: comparison with string literal results in unspecified behavior [-Waddress]
    4 |     if (argc > 1 && argv[1] == "-v") {
      |                             ^~
$ ./opcao -v                               « não mostrou a versão

O que significa: argv[1] == "-v" compara endereços (onde cada texto está na memória), e não as letras (aula Funções de String). Os dois textos estão em lugares diferentes, então nunca são "iguais".

Como consertar: if (strcmp(argv[1], "-v") == 0), com #include <string.h>.


Referência rápida

EscritaO que faz
int main(int argc, char *argv[])Recebe os argumentos da linha de comando
argcQuantos argumentos, contando o nome do programa (mínimo 1)
argv[0]O nome com que o programa foi chamado
argv[1] … argv[argc - 1]Os argumentos, sempre como strings
argv[argc]NULL
if (argc != 2) { fprintf(stderr, "Uso: %s X\n", argv[0]); return 1; }Mensagem de uso
strtol(argv[i], &fim, 10)Converte para inteiro (conferindo fim e errno)
strcmp(argv[i], "-v") == 0Compara um argumento com um texto
./prog "dois termos"No shell: um argumento com espaço

Exercícios

1. Quantos?

Quanto vale argc em ./copiar foto.jpg backup/foto.jpg?

argc =

2. O primeiro argumento de verdade

Complete para mostrar o primeiro argumento depois do nome do programa:

printf("%s\n", argv[]);

3. Com espaço

Em ./saudar "Ana Maria" Souza, qual é o texto de argv[1]?

4. A pasta tem a.txt e b.txt. Em ./args *.txt, o que o programa recebe?

5. O que atoi("doze") devolve?

6. Por que conferir argc antes de usar argv[1]?

Missão final: somar os argumentos

Missão: o programa deve somar todos os números passados como argumentos. A mensagem de uso já está pronta. Complete o laço: percorra de argv[1] até argv[argc - 1], converta cada um com strtol e, se algum não for número, mostre "X" não é um número no stderr e termine com 1. Com os argumentos 10 20 30 5, a saída deve ser:

Soma: 65
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Uso: %s NÚMERO...\n", argv[0]);
        return 1;
    }

    long soma = 0;
    // percorra os argumentos, converta e some

    printf("Soma: %ld\n", soma);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Uso: %s NÚMERO...\n", argv[0]);
        return 1;
    }

    long soma = 0;
    // percorra os argumentos, converta e some
    for (int i = 1; i < argc; i++) {
        char *fim;
        errno = 0;
        long v = strtol(argv[i], &fim, 10);
        if (fim == argv[i] || *fim != '\0' || errno == ERANGE) {
            fprintf(stderr, "%s: \"%s\" não é um número\n", argv[0], argv[i]);
            return 1;
        }
        soma += v;
    }

    printf("Soma: %ld\n", soma);
    return 0;
}

Depois de cumprir a missão, teste no campo Argumentos: 10 vinte (deve reclamar do "vinte" e terminar com 1) e nada (a mensagem de uso). No laboratório, o mesmo programa dá:

$ ./somar 10 20 30 5
Soma: 65
$ ./somar 10 vinte
./somar: "vinte" não é um número
$ ./somar
Uso: ./somar NÚMERO...
$ echo $?
1

Resumo

  • Para receber argumentos: int main(int argc, char *argv[]). argc conta, argv lista.
  • argv[0] é o nome do programa como foi digitado; os argumentos vão de argv[1] a argv[argc - 1]; argv[argc] é NULL.
  • Todo argumento é texto. Converta com strtol e confira fim e errno; o atoi transforma lixo em 0 calado.
  • Confira argc antes de usar argv[i] e mostre uma mensagem de uso no stderr, com return 1.
  • O shell separa os argumentos antes: espaços separam, aspas juntam, $VAR e * são trocados. No editor, o campo Argumentos (e o data-args) entende aspas, mas não expande $ nem *.