C# Argumentos da Linha de Comando

No terminal, quase todo comando recebe informações junto com o nome: ls -l, cp foto.png backup/, mkdir projetos. Essas palavras depois do comando são os argumentos. Os programas da Ana em C# também podem recebê-los: em vez de perguntar o nome com Console.ReadLine, o programa já começa sabendo, porque a Ana escreveu dotnet run ola.cs -- Ana. Dentro do programa, os argumentos chegam numa variável pronta, chamada args. Nesta aula você vai ver como mandá-los, como o terminal os separa e como usá-los sem o programa quebrar.

Você vai aprender
  • O que é o args e como ler cada argumento com args[0], args[1]…
  • Como mandar argumentos com dotnet run arquivo.cs -- …, e para que serve o --.
  • Como o terminal corta a linha em argumentos: espaços, aspas e o *.
  • Por que conferir args.Length antes de usar, e como mostrar uma mensagem de uso com return 1.
  • Como transformar um argumento em número (e por que 10 + 20 deu 1020).
  • Como passar argumentos num projeto e quando usar args ou ReadLine.

Todos os programas foram rodados com o .NET 10 (dotnet --version = 10.0.112, C# 14) na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs. Antes desta aula, é bom ter visto Ler do Teclado e o for.


A ideia: o bilhete junto com o pedido

Há dois jeitos de pedir um bolo na confeitaria:

  • Você chega e a atendente pergunta: "Qual sabor? Para quantas pessoas? Que nome vai no bolo?". Você responde uma coisa de cada vez. Esse é o Console.ReadLine, da aula anterior: uma conversa com o programa já rodando.
  • Você entrega um bilhete: "chocolate, 20 pessoas, Ana". A atendente lê tudo de uma vez, antes de começar, e não precisa perguntar nada. Esses são os argumentos: o que você manda na hora de mandar rodar.

O bilhete tem uma vantagem: dá para repetir o pedido igualzinho, sem digitar as respostas de novo. É por isso que os comandos do terminal (e os scripts do Guia Bash) usam argumentos.


1. O args

Num programa de arquivo único, como os da Ana, existe uma variável que você não criou: args (de arguments, argumentos). Ela é um array de strings, string[]: uma fileira de textos, um para cada argumento. Arrays têm aula própria logo adiante (Arrays); por enquanto, basta saber três coisas:

  • args.Length diz quantos argumentos chegaram;
  • args[0] é o primeiro, args[1] o segundo, e assim por diante: a contagem começa no 0;
  • cada um é uma string, mesmo que pareça um número.
Console.WriteLine($"Recebi {args.Length} argumento(s):");
for (int i = 0; i < args.Length; i++)
{
    Console.WriteLine($"args[{i}] = {args[i]}");
}
args
Criada pelo C# antes da primeira linha do seu programa, já com os argumentos dentro. Você só usa.
args.Length
"Comprimento": quantos argumentos há. Pode ser 0.
for (int i = 0; i < args.Length; i++)
Passa pelas posições 0, 1, 2… até a última. Com args.Length igual a 0, o laço nem começa.
args[i]
O argumento da posição i.
$ dotnet run args.cs
Recebi 0 argumento(s):
$ dotnet run args.cs -- Ana 29
Recebi 2 argumento(s):
args[0] = Ana
args[1] = 29

Na primeira vez, a Ana não mandou nada: args chegou vazio. Na segunda, tudo o que veio depois do -- foi para o programa: Ana e 29, cada um numa posição.

De onde vem o args?

Nos programas antigos (e em muitos projetos), o programa começa num método static void Main(string[] args), e o args é o parâmetro dele. Com as instruções de nível superior C# 9, o Main fica escondido, mas o args continua lá, com o mesmo nome. A aula Program.cs e o Main mostra os dois lado a lado.


2. Para que serve o --

Na linha dotnet run args.cs -- Ana 29, há dois programas recebendo coisas: o dotnet run, que compila e roda, e o programa da Ana. O -- é a divisa: o que vem antes é para o dotnet; o que vem depois é para o args.

$ dotnet run args.cs Ana 29                « sem o --
Recebi 2 argumento(s):
args[0] = Ana
args[1] = 29

Com palavras simples, funciona até sem o --: o dotnet percebe que Ana e 29 não são opções dele e as repassa. O problema aparece quando um argumento começa com -, porque aí o dotnet acha que é uma opção dele:

$ dotnet run args.cs -v
Argumento obrigatório ausente para a opção: '-v'.

Description:
  Comando Run do .NET

Uso:
  dotnet run [<applicationArguments>...] [options]
…                                          « e mais umas 85 linhas de ajuda
$ dotnet run args.cs -- -v
Recebi 1 argumento(s):
args[0] = -v

Sem o --, o -v foi entendido como a opção "verbosidade" do dotnet run, que precisa de um valor, e o programa da Ana nem rodou. Com o --, ele chegou intacto. Com --help é igual: sem o --, aparece a ajuda do dotnet.

Regra do guia: sempre escreva o --

Use dotnet run arquivo.cs -- argumentos, mesmo quando não seria preciso. Fica claro para quem lê o que é de quem, e o programa não muda de comportamento no dia em que um argumento começar com -.


3. Como o terminal corta a linha

Quem separa a linha em argumentos não é o C#: é o terminal (o bash), antes de o programa começar. As regras são as do Guia Bash:

$ dotnet run args.cs -- Ana Maria   29
Recebi 3 argumento(s):
args[0] = Ana
args[1] = Maria          « o espaço separa: viraram dois
args[2] = 29
$ dotnet run args.cs -- "Ana Maria" 29
Recebi 2 argumento(s):
args[0] = Ana Maria      « as aspas juntam
args[1] = 29
$ dotnet run args.cs -- ""
Recebi 1 argumento(s):
args[0] =                « um argumento vazio, mas existe
$ dotnet run args.cs -- *.txt
Recebi 3 argumento(s):
args[0] = a.txt
args[1] = b.txt          « o bash trocou *.txt pelos nomes dos arquivos
args[2] = precos.txt
$ dotnet run args.cs -- "*.txt"
Recebi 1 argumento(s):
args[0] = *.txt
Você escreveO programa recebePor quê
Ana Maria 293 argumentos: Ana, Maria, 29Um ou mais espaços separam. Os espaços em si não chegam.
"Ana Maria" 292: Ana Maria, 29Entre aspas, o espaço faz parte do argumento. As aspas não chegam.
""1: o texto vazioAspas vazias são um argumento sem nenhuma letra.
*.txtos nomes dos arquivos .txt da pastaO * é um curinga do bash: ele troca pelos nomes antes de rodar.
"*.txt" ou '*.txt'1: *.txtEntre aspas, o * fica como está.

Missão: esta caixa roda o programa com o argumento Ana (como dotnet run ola.cs -- Ana). Troque o "mundo" pelo primeiro argumento, args[0], para a saída ser Olá, Ana!.

Olá, Ana!
Console.WriteLine("Olá, mundo!");
Console.WriteLine($"Olá, {args[0]}!");

4. Confira args.Length antes de usar

O programa da missão funciona quando a Ana manda o nome. E quando ela esquece?

Console.WriteLine($"Olá, {args[0]}!");
$ dotnet run ola-args.cs -- Ana
Olá, Ana!
$ dotnet run ola-args.cs
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
   at Program.<Main>$(String[] args) in /home/ana/cs/ola-args.cs:line 1
$ echo $?
134

IndexOutOfRangeException = "exceção de índice fora do intervalo": Index was outside the bounds of the array, "o índice estava fora dos limites do array". Sem argumentos, o args tem 0 posições, e não existe args[0]. O programa quebrou na linha 1, com o código de saída 134. O compilador não tem como avisar: ele não sabe quantos argumentos você vai mandar.

Os comandos do terminal resolvem isso com uma mensagem de uso: se faltar argumento, eles explicam como usar e terminam com um código de erro. Em C#:

if (args.Length == 0)
{
    Console.Error.WriteLine("Uso: dotnet run ola2.cs -- NOME");
    return 1;
}
Console.WriteLine($"Olá, {args[0]}!");
return 0;
if (args.Length == 0)
Nenhum argumento? Então não dá para continuar.
Console.Error.WriteLine(…)
Escreve na saída de erros (stderr), e não na saída normal. Na tela, as duas aparecem iguais, mas o terminal consegue separá-las (no Guia Bash, o 2>). Mensagens de erro e de uso vão para lá, por costume.
return 1;
Termina o programa na hora, com o código de saída 1. Para o terminal (e para os scripts), 0 quer dizer "deu certo" e qualquer outro número quer dizer "deu errado".
return 0;
No fim, "deu certo". Quando o programa usa return com número em algum lugar, ele precisa de um return com número no fim também (sem ele, dá o erro CS0161 da seção "Quando dá errado").
$ dotnet run ola2.cs
Uso: dotnet run ola2.cs -- NOME
$ echo $?
1
$ dotnet run ola2.cs -- Bia
Olá, Bia!
$ echo $?
0

Agora, quem esquece o nome recebe uma explicação, em vez de dez linhas em inglês. E o código 1 avisa um script que algo deu errado.


5. Argumentos são texto: converta com TryParse

Todo argumento chega como string, mesmo os que parecem números. Veja o que acontece se a Ana somar dois deles direto:

$ cat junta.cs
Console.WriteLine(args[0] + args[1]);
$ dotnet run junta.cs -- 10 20
1020

Com duas string, o + junta os textos (aula Strings): "10" + "20" = "1020". Nenhum erro, nenhum aviso: só o resultado errado. Para fazer conta, converta antes com int.TryParse, como na aula Ler do Teclado:

int soma = 0;
foreach (string arg in args)
{
    if (int.TryParse(arg, out int numero))
    {
        soma += numero;
    }
    else
    {
        Console.WriteLine($"Ignorado: \"{arg}\" não é um número.");
    }
}
Console.WriteLine($"Soma: {soma}");
foreach (string arg in args)
Passa por todos os argumentos, um de cada vez, sem precisar de índice (aula foreach). Funciona com qualquer quantidade, até zero.
int.TryParse(arg, out int numero)
Tenta converter. Se der, numero recebe o valor e é somado.
$ dotnet run soma-args.cs -- 10 20 doze 12
Ignorado: "doze" não é um número.
Soma: 42

Diferente do ReadLine, um argumento nunca é null: o args é um string[], sem ?. O que pode acontecer é ele faltar (seção 4) ou ser um texto vazio ("").

Missão: esta caixa roda com os argumentos 10 20 12. O programa mostra Soma: 102012, porque junta os textos. Conserte para mostrar Soma: 42, convertendo cada argumento com int.TryParse. Melhor ainda se funcionar com qualquer quantidade de números.

Soma: 42
Console.WriteLine($"Soma: {args[0] + args[1] + args[2]}");
int soma = 0;
foreach (string arg in args)
{
    if (int.TryParse(arg, out int numero))
    {
        soma += numero;
    }
}
Console.WriteLine($"Soma: {soma}");

6. Argumentos num projeto

Num projeto criado com dotnet new console (aula Projetos), é igual, só que sem o nome do arquivo: o dotnet run roda o projeto da pasta, e o -- separa os argumentos.

$ dotnet run -- Ana 29
Recebi 2 argumento(s):
args[0] = Ana
args[1] = 29
$ ./bin/Debug/net10.0/Saudacao Ana 29        « o programa já compilado, sem o dotnet run
Recebi 2 argumento(s):
args[0] = Ana
args[1] = 29

Na segunda linha, a Ana rodou direto o programa que o dotnet run tinha compilado na pasta bin. Aí não há dotnet no meio, então não há --: tudo depois do nome do programa vai para o args, como em qualquer comando do terminal. É assim que o programa vai ser usado depois de pronto (aula Publicar o Programa).


7. args ou ReadLine?

Argumentos (args)Entrada (Console.ReadLine)
Quando chegaAntes de o programa começar, tudo de uma vezDurante o programa, quando ele pergunta
Tipostring[] (nunca null; pode faltar)string? (null no fim da entrada)
Bom paraOpções e nomes de arquivo; repetir o mesmo comando; scriptsConversas: menus, jogos, perguntas que dependem da resposta anterior
Exemplodotnet run calc.cs -- 7 x 6Primeiro número? 7

Muitos programas usam os dois: se o argumento veio, usam; se não veio, perguntam com ReadLine.


Quando dá errado

Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.

O que significa: o programa usou args[0] (ou args[1]…) e esse argumento não foi mandado. O programa termina com o código 134.

Como consertar: confira args.Length antes, e mostre uma mensagem de uso com Console.Error.WriteLine e return 1.

A soma deu 1020 em vez de 30

O que significa: os argumentos são string, e o + entre textos junta os textos.

Como consertar: converta cada um com int.TryParse (ou decimal.TryParse) antes da conta. Se você tentar guardar direto (int n = args[0];), o compilador recusa com error CS0029: Não é possível converter implicitamente tipo "string" em "int".

error CS0161: "<top-level-statements-entry-point>": nem todos os caminhos de código retornam um valor
$ cat semret.cs
if (args.Length == 0)
{
    return 1;
}
Console.WriteLine($"Olá, {args[0]}!");
$ dotnet run semret.cs -- Ana
/home/ana/cs/semret.cs(1,1): error CS0161: "<top-level-statements-entry-point>": nem todos os caminhos de código retornam um valor

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

O que significa: "nem todos os caminhos devolvem um valor" (not all code paths return a value). O programa tem um return 1;, então o C# entende que ele termina sempre com um número. Mas, quando há argumento, ele chega ao fim sem return. O nome estranho, <top-level-statements-entry-point>, é o "ponto de entrada das instruções de nível superior": o Main escondido.

Como consertar: ponha return 0; na última linha.

O dotnet mostrou a própria ajuda, ou Argumento obrigatório ausente para a opção: '-v'.

O que significa: um argumento que começa com - ficou antes do -- (ou não havia --), e o dotnet run pensou que era uma opção dele.

Como consertar: escreva sempre dotnet run arquivo.cs -- argumentos.

Chegaram argumentos a mais (um nome virou dois, um * virou nomes de arquivo)

O que significa: o terminal cortou a linha nos espaços e trocou o * pelos arquivos da pasta. Por exemplo, dotnet run calc.cs -- 7 * 6 chega como 7, depois os nomes de todos os arquivos da pasta, depois 6.

Como consertar: ponha entre aspas o que deve chegar inteiro: "Ana Maria", "*". Ou escolha símbolos que o terminal não mexe, como x para a multiplicação.


Referência rápida

EscritaO que faz
dotnet run arq.cs -- a b cRoda arq.cs com 3 argumentos
dotnet run -- a b cO mesmo, num projeto
argsstring[] com os argumentos (vazio se não veio nenhum)
args.LengthQuantos argumentos chegaram
args[0]O primeiro argumento (quebra se não houver)
foreach (string a in args)Passa por todos
int.TryParse(args[0], out int n)Converte um argumento em número
Console.Error.WriteLine("Uso: …")Mensagem de uso na saída de erros
return 1; / return 0;Termina o programa com código de erro / de sucesso
"Ana Maria"Um argumento só, com espaço

Exercícios

1. Quantos chegaram?

Complete para mostrar quantos argumentos o programa recebeu:

Console.WriteLine(args.);

2. O segundo argumento

A Ana rodou dotnet run notas.cs -- Bia 8 9. Complete para pegar o 8:

string nota = args[];

3. Contar argumentos

Quantos argumentos o programa recebe com dotnet run args.cs -- "Rua das Flores" 42 ""?

argumentos

4. Para que serve o -- em dotnet run ola.cs -- -v?

5. O programa faz Console.WriteLine(args[0] + args[1]); e roda com -- 2 3. O que aparece?

6. Um programa com Console.WriteLine(args[0]); rodou sem nenhum argumento. O que acontece?

Missão final: a calculadora de linha de comando

Missão: a calculadora da Ana recebe três argumentos: um número, a operação e outro número (dotnet run calc.cs -- 12 + 30). Ela já sabe somar e subtrair, e já confere os argumentos. Esta caixa roda com 7 x 6, e a resposta é Operação desconhecida: x, com código 1. Acrescente a multiplicação, com a letra x, para a saída ser 7 x 6 = 42 (com código 0).

7 x 6 = 42
if (args.Length != 3)
{
    Console.Error.WriteLine("Uso: dotnet run calc.cs -- NÚMERO OPERAÇÃO NÚMERO");
    return 1;
}
if (!int.TryParse(args[0], out int a) || !int.TryParse(args[2], out int b))
{
    Console.Error.WriteLine("Os dois números têm de ser inteiros.");
    return 1;
}
string operacao = args[1];
if (operacao == "+")
{
    Console.WriteLine($"{a} + {b} = {a + b}");
}
else if (operacao == "-")
{
    Console.WriteLine($"{a} - {b} = {a - b}");
}
else
{
    Console.Error.WriteLine($"Operação desconhecida: {operacao}");
    return 1;
}
return 0;
if (args.Length != 3)
{
    Console.Error.WriteLine("Uso: dotnet run calc.cs -- NÚMERO OPERAÇÃO NÚMERO");
    return 1;
}
if (!int.TryParse(args[0], out int a) || !int.TryParse(args[2], out int b))
{
    Console.Error.WriteLine("Os dois números têm de ser inteiros.");
    return 1;
}
string operacao = args[1];
if (operacao == "+")
{
    Console.WriteLine($"{a} + {b} = {a + b}");
}
else if (operacao == "-")
{
    Console.WriteLine($"{a} - {b} = {a - b}");
}
else if (operacao == "x")
{
    Console.WriteLine($"{a} x {b} = {a * b}");
}
else
{
    Console.Error.WriteLine($"Operação desconhecida: {operacao}");
    return 1;
}
return 0;

No terminal da Ana, a calculadora pronta fica assim. Repare no 7 * 6: o bash trocou o * pelos nomes dos arquivos da pasta, chegaram mais de 3 argumentos, e o programa mostrou a mensagem de uso. É por isso que a calculadora usa x.

$ dotnet run calc.cs -- 12 + 30
12 + 30 = 42
$ dotnet run calc.cs -- 7 x 6
7 x 6 = 42
$ dotnet run calc.cs -- 7 * 6
Uso: dotnet run calc.cs -- NÚMERO OPERAÇÃO NÚMERO
$ dotnet run calc.cs -- sete x 6
Os dois números têm de ser inteiros.
$ echo $?
1

Resumo

  • Argumentos são as palavras que você manda junto com o comando. No C#, eles chegam no args, um string[] criado antes da primeira linha do programa.
  • Mande com dotnet run arquivo.cs -- a b c (ou dotnet run -- a b c num projeto). O -- separa o que é do dotnet do que é do seu programa: use sempre.
  • Quem corta a linha é o terminal: espaços separam, aspas juntam, e o * vira nomes de arquivos.
  • args.Length diz quantos chegaram. Confira antes de usar args[0], ou o programa quebra com IndexOutOfRangeException (código 134). Para faltas, mostre uma mensagem de uso no Console.Error e use return 1.
  • Todo argumento é texto: converta com TryParse antes de fazer conta, ou 10 + 20 vira 1020.