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.
- O que são
argceargve o que há em cada posição deargv. - Por que
argv[0]é o nome do próprio programa. - Como converter um argumento em número com
strtol(e por que oatoiengana). - 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
argccomo 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ção | argv[0] | argv[1] | argv[2] | argv[3] |
|---|---|---|---|---|
Conteúdo para ./args Ana 29 | "./args" | "Ana" | "29" | NULL |
argv[argc] é sempre NULL (o padrão C garante).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.
NOMEem 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;
}
| Teste | Pega o caso |
|---|---|
fim == argv[1] | Nenhum algarismo: sete, ou uma string vazia |
*fim != '\0' | Sobrou texto depois do número: 7x, 7.5 |
errno == ERANGE | Nú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ê digita | O programa recebe | Por quê |
|---|---|---|
Ana Maria | dois argumentos | O espaço separa argumentos |
"Ana Maria" ou a\ b | um argumento, com espaço | Aspas (ou a barra invertida) juntam |
'preço: $5' | preço: $5 | Aspas simples: nada dentro é interpretado |
"$HOME" | /home/ana | Aspas duplas deixam o shell trocar variáveis (aula Variáveis de Ambiente) |
*.jpg | a lista de arquivos .jpg | O 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.)
$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
| Forma | Quando 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) C23 | Usa 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.
$ ./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 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.
== 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
| Escrita | O que faz |
|---|---|
int main(int argc, char *argv[]) | Recebe os argumentos da linha de comando |
argc | Quantos 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") == 0 | Compara 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?
2. O primeiro argumento de verdade
Complete para mostrar o primeiro argumento depois do nome do programa:
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[]).argcconta,argvlista. argv[0]é o nome do programa como foi digitado; os argumentos vão deargv[1]aargv[argc - 1];argv[argc]éNULL.- Todo argumento é texto. Converta com
strtole confirafimeerrno; oatoitransforma lixo em 0 calado. - Confira
argcantes de usarargv[i]e mostre uma mensagem de uso nostderr, comreturn 1. - O shell separa os argumentos antes: espaços separam, aspas juntam,
$VARe*são trocados. No editor, o campo Argumentos (e odata-args) entende aspas, mas não expande$nem*.