C# Rodar com dotnet run
Na aula anterior, você rodou o primeiro programa com dotnet run ola.cs. Esse comando é a ferramenta que você vai usar em todo o Nível 1 e no Nível 2, então vale conhecê-lo bem. Nesta aula você descobre o que ele faz por dentro, por que a primeira vez demora e a segunda não, como passar informações para o programa, o que é o código de saída e como transformar um arquivo .cs num script que roda sozinho.
- O que são os aplicativos de arquivo único (file-based apps), a novidade do .NET 10.
- O que o
dotnet run ola.csfaz por dentro e onde ele guarda o programa compilado. - Por que a primeira vez demora, e por que os avisos do compilador só aparecem quando ele compila de novo.
- Como passar argumentos ao programa, e para que serve o
--. - Os códigos de saída
0,1e134. - As diretivas
#:packagee#:property, no topo do arquivo. - Como rodar um
.cscomo script, com#!/usr/bin/env dotnet. - Quando é hora de transformar o arquivo num projeto.
As saídas são da máquina da Ana, com o .NET SDK 10.0.112, na pasta ~/cs. Os projetos com dotnet new console têm aula própria: Projetos (dotnet new console).
A ideia: a padaria que guarda o bolo
Imagine uma padaria que recebe a sua receita, assa o bolo e entrega. Se amanhã você aparecer com a mesma receita, a padaria não assa de novo: ela já guardou um bolo igual na geladeira e entrega na hora. Se você riscar uma linha da receita, ela percebe e assa um bolo novo.
O dotnet run ola.cs é essa padaria. Na primeira vez, ele compila o arquivo e guarda o programa pronto numa pasta escondida. Nas outras vezes, ele confere se o ola.cs mudou: se não mudou, roda o programa guardado; se mudou, compila de novo.
1. O comando
$ dotnet run ola.cs Olá, mundo!
- dotnet
- O comando do .NET SDK.
- run
- "Rodar": compila (se precisar) e roda.
- ola.cs
- O arquivo com o programa. Ele tem de estar na pasta atual (ou você escreve o caminho:
dotnet run ~/cs/ola.cs).
Até o .NET 9, o dotnet run só sabia rodar projetos: uma pasta com um arquivo .csproj que descreve o programa (aula Projetos). O .NET 10 aprendeu a rodar um arquivo .cs sozinho, sem projeto. A Microsoft chama isso de aplicativo baseado em arquivo (file-based app). É o jeito mais simples de começar, e é o que o guia usa nos Níveis 1 e 2.
Três jeitos de escrever
$ dotnet run ola.cs « o jeito do guia Olá, mundo! $ dotnet ola.cs « atalho: sem o run Olá, mundo! $ dotnet run --file ola.cs « explícito: "isto é um arquivo" Olá, mundo!
| Forma | Quando usar |
|---|---|
dotnet run ola.cs | Sempre, no guia. Deixa claro que você está rodando. |
dotnet ola.cs | Atalho para quem já está acostumado. Faz o mesmo. |
dotnet run --file ola.cs | Quando a pasta também tem um projeto e você quer deixar claro que é o arquivo (veja Quando dá errado). |
2. O que acontece por dentro
Quando você roda dotnet run ola.cs, o SDK faz isto:
- Monta um projeto invisível para o arquivo, com as configurações padrão do .NET 10 (parecidas com as de um projeto
dotnet new console). - Confere se precisa compilar. Se o programa já foi compilado e o
ola.csnão mudou, pula para o passo 4. - Compila com o Roslyn. Se houver erro, mostra a mensagem e para aqui.
- Roda o programa compilado, que escreve na tela.
Onde fica o programa compilado
Na pasta da Ana, nada muda: só o ola.cs continua lá. O programa compilado vai para uma pasta escondida. O dotnet build, que compila sem rodar, mostra o endereço:
$ dotnet build ola.cs Determinando os projetos a serem restaurados... Todos os projetos estão atualizados para restauração. ola -> /home/ana/.local/share/dotnet/runfile/ola-d3e0b993ea942ae2aa43a2c132f9667a87cb5c1410a99c454aae33ed39b33823/bin/debug/ola.dll
Cada arquivo que você roda ganha uma pasta própria em ~/.local/share/dotnet/runfile, com o nome do arquivo e um código comprido, calculado a partir do caminho do arquivo (por isso dois ola.cs em pastas diferentes não se misturam):
$ ls ~/.local/share/dotnet/runfile | head -3 antigo-dd3e9aa2d82fb4bbb24b83889955c931d21639ff05141ce927617435495868b8 args-538ef144dfff177e98d916b3ae5074306e8a7492f32d9b712d144636630addb0 aspas2-50be6b2113d0c1ce0f72ed21813b15054498c67a37acb8b811ee496d04c3fef8
Dentro de cada uma está o ola.dll e os arquivos que a aula .NET: SDK, Runtime e CLR abriu um por um. Você nunca precisa mexer nessa pasta.
A primeira vez e as outras
O comando time, na frente de outro comando, mede quanto tempo ele levou. Veja um arquivo novo, tempo.cs, rodado duas vezes seguidas:
$ time dotnet run tempo.cs Testando o tempo real 0m3,800s « 1.ª vez: compilou (quase 4 segundos) user 0m0,405s sys 0m0,060s $ time dotnet run tempo.cs Testando o tempo real 0m0,281s « 2.ª vez: só rodou (menos de 0,3 s) user 0m0,223s sys 0m0,060s
A linha real é o tempo de relógio. Na primeira vez, o SDK precisou acordar o compilador e compilar: quase 4 segundos. Na segunda, o arquivo não tinha mudado, e ele só rodou o programa guardado. Depois que você muda o arquivo, ele compila de novo, mas bem mais rápido, porque o compilador fica de prontidão em segundo plano por um tempo:
$ echo 'Console.WriteLine("Tchau!");' >> ola.cs « acrescenta uma linha no fim do ola.cs
$ time dotnet run ola.cs
Olá, mundo!
Tchau! « mudou o arquivo: compilou de novo
real 0m0,516s
user 0m0,316s
sys 0m0,073s
O editor do guia não guarda nada: ele compila o código de novo toda vez que você clica em Rodar.
3. Os avisos só aparecem quando compila
Além dos erros, que impedem o programa de rodar, o compilador dá avisos (warning): "isto funciona, mas parece engano". Por exemplo, uma variável criada e nunca usada (variáveis são da aula Variáveis):
int idade;
Console.WriteLine("Programa com aviso");
$ dotnet run aviso.cs /home/ana/cs/aviso.cs(1,5): warning CS0168: A variável "idade" está declarada, mas nunca é usada Programa com aviso « com o aviso, o programa rodou $ dotnet run aviso.cs Programa com aviso « da segunda vez, o aviso sumiu!
O aviso sumiu na segunda vez porque o arquivo não mudou e o dotnet run não compilou de novo; quem dá o aviso é o compilador. O problema continua lá. Para obrigar o dotnet run a compilar de novo, e ver os avisos, use --no-cache ("sem o guardado"):
$ dotnet run --no-cache aviso.cs /home/ana/cs/aviso.cs(1,5): warning CS0168: A variável "idade" está declarada, mas nunca é usada Programa com aviso
Leve os avisos a sério desde já: quase sempre eles apontam um engano de verdade. A aula Avisos do Compilador mostra os mais comuns.
Missão: rode e leia o aviso CS0168. Depois apague a linha da variável que nunca é usada e troque o texto para Programa sem aviso. Rode de novo: o aviso tem de sumir.
int idade;
Console.WriteLine("Programa com aviso");
Console.WriteLine("Programa sem aviso");
4. Passar argumentos ao programa
Você pode mandar informações para o programa direto no comando, depois do nome do arquivo. Elas se chamam argumentos e chegam ao programa numa lista chamada args. Este programa conta quantos argumentos recebeu e mostra cada um (o foreach é da aula foreach, e o args tem aula própria, Argumentos da Linha de Comando):
Console.WriteLine($"Recebi {args.Length} argumentos");
foreach (var a in args) Console.WriteLine(a);
$ dotnet run args.cs Ana 29 Recebi 2 argumentos Ana 29 $ dotnet run args.cs -- "Ana Souza" 29 « com aspas, "Ana Souza" é um argumento só Recebi 2 argumentos Ana Souza 29
- Ana 29
- Dois argumentos, separados por espaço.
- "Ana Souza"
- Entre aspas, o espaço não separa: é um argumento só. As aspas são do terminal e não chegam ao programa.
- --
- Os dois tracinhos dizem ao
dotnet: "daqui para a frente, nada é para você, é tudo para o programa". Sem ele, funciona na maioria das vezes, mas um argumento que começa com-pode ser "roubado" pelodotnet(veja Quando dá errado). Use sempre que tiver dúvida.
A caixa abaixo passa os argumentos Ana 29 para o programa, como se você tivesse digitado dotnet run args.cs Ana 29:
Experimente: rode e compare com a saída da Ana. Depois acrescente uma linha no fim, Console.WriteLine("Fim da lista");, e rode de novo.
Console.WriteLine($"Recebi {args.Length} argumentos");
foreach (var a in args) Console.WriteLine(a);
5. O código de saída passa adiante
O código de saída (aula Olá, Mundo) do dotnet run é o do seu programa. Assim, um script que roda o seu programa sabe se ele deu certo. Há três casos para conhecer:
$ cat saida3.cs
Console.WriteLine("Saindo com 3");
return 3;
$ dotnet run saida3.cs; echo "código: $?"
Saindo com 3
código: 3 « o número do return
$ dotnet run semponto.cs; echo "código: $?"
/home/ana/cs/semponto.cs(1,33): error CS1002: ; esperado
Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
código: 1 « a compilação falhou: o programa nem rodou
$ cat excecao.cs
int[] notas = { 8, 9 };
Console.WriteLine(notas[2]);
$ dotnet run excecao.cs; echo "código: $?"
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
at Program.<Main>$(String[] args) in /home/ana/cs/excecao.cs:line 2
código: 134 « o programa rodou e "quebrou"
| Código | Quer dizer |
|---|---|
0 | O programa rodou até o fim, sem problemas |
1 | A compilação falhou (erro CSxxxx), ou o dotnet não achou o arquivo |
o número do return | O programa escolheu o código (return 3;) |
134 | O programa compilou, rodou e parou no meio com uma exceção não tratada |
O último caso é diferente dos outros: o compilador não viu problema nenhum, mas, ao rodar, o programa tentou pegar a terceira nota (notas[2]) de uma lista que só tem duas. A mensagem sai em inglês (Unhandled exception = "exceção não tratada"; Index was outside the bounds of the array = "o índice estava fora dos limites do array") e diz a linha: excecao.cs:line 2. O 134 é o código que o Linux dá a um programa que foi abortado. As exceções têm uma seção inteira no Nível 2, a partir de Exceções.
Missão: rode e veja a exceção e o código de saída 134. A lista tem duas notas, nas posições 0 e 1 (em C#, a contagem começa do zero). Troque o 2 por 1 para mostrar a última nota, 9, e rode de novo: o código de saída tem de virar 0.
int[] notas = { 8, 9 };
Console.WriteLine(notas[2]);
int[] notas = { 8, 9 };
Console.WriteLine(notas[1]);
6. As diretivas #: no topo do arquivo .NET 10
Um projeto guarda as suas configurações no arquivo .csproj. Um arquivo solto não tem .csproj, então o .NET 10 criou outro lugar para elas: linhas especiais no topo do .cs, que começam com #: e se chamam diretivas. As duas mais úteis:
| Diretiva | O que faz | No projeto, seria |
|---|---|---|
#:package Nome@versão | Usa um pacote do NuGet, a "loja" de bibliotecas do .NET | <PackageReference> (aula Pacotes NuGet) |
#:property Nome=valor | Muda uma configuração do build, como a versão do C# | uma linha no <PropertyGroup> (aula O Arquivo .csproj) |
Um exemplo com #:package: a biblioteca Humanizer transforma números, datas e nomes em texto "de gente". A primeira linha do arquivo pede a versão 2.14.1 dela:
#:package Humanizer@2.14.1
using Humanizer;
Console.WriteLine(TimeSpan.FromMinutes(90).Humanize(2));
Console.WriteLine("notas_da_ana".Humanize());
$ time dotnet run pacote.cs 1 hora, 30 minutos « 90 minutos, em português notas da ana « o nome com _ virou frase real 0m11,170s « 1.ª vez: baixou o pacote da internet user 0m6,069s sys 0m0,360s $ dotnet run pacote.cs 1 hora, 30 minutos « da segunda vez, já está no computador notas da ana
Na primeira vez, o dotnet baixou o pacote do site nuget.org e guardou em ~/.nuget/packages (por isso os 11 segundos); depois, ele usa a cópia guardada. O using Humanizer; é da aula Namespaces e using. A diretiva #:property aparece na aula Versões, para escolher a versão do C#. As diretivas precisam ficar no topo, antes de qualquer código. No editor do guia, que roda no navegador, a diretiva #:package não baixa nada, e o mesmo código dá o erro CS0246 (O nome do tipo ou do namespace "Humanizer" não pode ser encontrado): os exemplos com pacotes são para o terminal.
7. Rodar como um script
No Linux e no macOS, um arquivo de texto vira um comando quando tem duas coisas: a permissão de execução e uma primeira linha especial, o shebang, que diz qual programa deve lê-lo. O .NET 10 aceita o shebang num .cs:
#!/usr/bin/env dotnet
Console.WriteLine("Rodei como um script!");
$ chmod +x script.cs « dá permissão de execução $ ls -l script.cs -rwxrwxr-x 1 ana ana 66 out 17 12:42 script.cs « o x quer dizer "executável" $ ./script.cs « roda direto, sem escrever dotnet run Rodei como um script!
- #!
- O shebang (o nome vem de hash + bang, os nomes em inglês do
#e do!). Tem de ser os dois primeiros caracteres do arquivo. - /usr/bin/env dotnet
- "Procure o programa
dotnete entregue este arquivo a ele." Oenvprocura odotnetonde quer que ele esteja instalado. - chmod +x
- Dá ao arquivo a permissão de execução (
x). Ochmodé explicado no Guia Bash. - ./script.cs
- Roda o arquivo desta pasta. Por baixo, é o mesmo
dotnet run script.cs, com a mesma pasta escondida e a mesma rapidez da segunda vez.
Para o compilador, a linha do shebang não é código: ele simplesmente a ignora. No Windows, que não usa shebang, o mesmo arquivo continua rodando com dotnet run script.cs.
8. Quando o arquivo cresce: vire projeto
O arquivo solto é ótimo para aprender e para programas pequenos. Quando o programa precisar de vários arquivos .cs, de testes ou de configurações mais elaboradas, é hora de um projeto. O .NET 10 converte o arquivo para você:
$ cd ~/conv $ dotnet project convert ola.cs « cria um projeto a partir do arquivo $ ls -R .: ola « a pasta do projeto, nova ola.cs « o arquivo original continua aqui ./ola: ola.cs « uma cópia do código ola.csproj « o arquivo do projeto
O comando não escreveu nada na tela (silêncio quer dizer sucesso) e criou a pasta ola com um ola.csproj, onde ficam as configurações que antes eram invisíveis (e as diretivas #:, se houvesse). A partir daí, você entra na pasta e usa dotnet run sem o nome do arquivo, como na aula Projetos (dotnet new console).
Arquivo solto ou projeto?
| Situação | Use |
|---|---|
| Aprender, testar uma ideia, um programa de um arquivo | Arquivo solto: dotnet run ola.cs |
| Um script para automatizar uma tarefa no Linux | Arquivo solto com shebang |
Um programa com vários arquivos .cs | Projeto: dotnet new console (ou dotnet project convert) |
| Testes automáticos, bibliotecas, soluções com vários projetos | Projeto (aulas do grupo Ferramentas) |
9. Limpar a pasta escondida
Cada arquivo que você roda deixa uma pasta em ~/.local/share/dotnet/runfile. Elas são pequenas, mas, depois de meses de estudo, viram centenas. O comando dotnet clean file-based-apps apaga as que não são usadas há 30 dias ou mais. Com --dry-run ("ensaio"), ele só diz o que faria:
$ dotnet clean file-based-apps --dry-run Procurando pastas para remover em: /home/ana/.local/share/dotnet/runfile Pastas que seriam removidas: 0 « tudo da Ana foi usado hoje
Para apagar de verdade, rode sem o --dry-run. Para mudar o prazo, use --days: --days 7 apaga as que estão paradas há uma semana. Não tem perigo: se você rodar o arquivo de novo, o dotnet compila de novo.
Quando dá errado
Não foi possível localizar um projeto para executar.
$ dotnet run ole.cs Não foi possível localizar um projeto para executar. Verifique se existe um projeto em /home/ana/cs ou passe o caminho para o projeto usando --project. $ echo $? 1
O que significa: o arquivo ole.cs não existe (a Ana errou o nome: é ola.cs). Quando o arquivo não existe, o dotnet run acha que ole.cs é um argumento e procura um projeto na pasta para rodar. Não acha, e reclama do projeto. A mensagem engana: o problema é o nome do arquivo. O mesmo acontece se você estiver na pasta errada, ou escrever o nome sem o .cs (dotnet run ola).
Como consertar: ls e confira o nome exato. A tecla Tab completa o nome do arquivo e evita o erro.
error CS8802: Apenas uma unidade de compilação pode ter instruções de nível superior.
$ ls obj ola.cs Program.cs Teste.csproj $ dotnet run ola.cs /home/ana/projetos/Teste/Program.cs(2,1): error CS8802: Apenas uma unidade de compilação pode ter instruções de nível superior. [/home/ana/projetos/Teste/Teste.csproj] Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O que significa: a Ana copiou o ola.cs para dentro da pasta de um projeto (a que tem o Teste.csproj). Quando a pasta tem um projeto, o dotnet run roda o projeto, e não o arquivo: o ola.cs virou um argumento. Pior: o projeto compila todos os .cs da pasta juntos, e agora há dois arquivos com instruções de nível superior (o Program.cs e o ola.cs). Só um pode ter ("apenas uma unidade de compilação", isto é, um arquivo).
Como consertar: não misture arquivos soltos com projetos. Guarde os arquivos soltos numa pasta só deles (como a ~/cs da Ana) e os projetos em outra (~/projetos).
- foi "roubado" pelo dotnet
$ dotnet run args.cs --help | head -4 Description: Comando Run do .NET Uso: $ dotnet run args.cs -- --help Recebi 1 argumentos --help
O que significa: o --help era para o programa, mas o dotnet run achou que era para ele e mostrou a própria ajuda. O programa nem rodou.
Como consertar: ponha -- antes dos argumentos do programa: tudo o que vem depois dele vai direto para o args.
./script.cs: Permissão negada
$ ./semx.cs bash: ./semx.cs: Permissão negada $ echo $? 126
O que significa: o arquivo tem o shebang, mas não tem a permissão de execução. O código 126 é o do terminal para "achei, mas não posso executar".
Como consertar: chmod +x semx.cs. Ou rode do jeito normal, dotnet run semx.cs, que não precisa de permissão.
env: ‘dotnet\r’: Arquivo ou diretório inexistente
$ ./crlf.cs env: ‘dotnet\r’: Arquivo ou diretório inexistente env: usa -[v]S para passar opções em linhas shebang $ echo $? 127
O que significa: o arquivo foi salvo no Windows, onde cada linha termina com dois caracteres invisíveis (\r\n), e não só com \n, como no Linux. O \r grudou no fim do shebang, e o env procurou um programa chamado dotnet\r, que não existe. (A segunda linha é só uma dica do env que não tem a ver com o problema.)
Como consertar: converta o fim das linhas: no VS Code, clique em CRLF no canto de baixo da janela e escolha LF; no terminal, sed -i 's/\r$//' crlf.cs. O dotnet run crlf.cs funciona mesmo com \r\n; só o shebang se confunde.
Referência rápida
| Comando | O que faz |
|---|---|
dotnet run ola.cs | Compila (se precisar) e roda o arquivo |
dotnet ola.cs | O mesmo, mais curto |
dotnet run --file ola.cs | O mesmo, dizendo explicitamente que é um arquivo |
dotnet run ola.cs -- a b c | Passa a, b e c para o args |
dotnet run --no-cache ola.cs | Compila de novo mesmo sem mudança (e mostra os avisos) |
dotnet run ola.cs -c Release | Compila na configuração de entrega, otimizada |
dotnet build ola.cs | Só compila, e diz onde ficou o .dll |
dotnet project convert ola.cs | Cria um projeto a partir do arquivo |
dotnet clean file-based-apps | Apaga as pastas escondidas paradas há 30 dias |
#:package Nome@versão | Usa um pacote NuGet (no topo do arquivo) |
#:property Nome=valor | Muda uma configuração do build (no topo do arquivo) |
#!/usr/bin/env dotnet + chmod +x | Roda o arquivo como script: ./ola.cs |
Exercícios
1. Compilar de novo
Complete o comando que obriga o dotnet run a compilar de novo, para ver os avisos:
2. Argumento com traço
Complete o comando para o programa receber o argumento -v, sem o dotnet roubá-lo:
3. O shebang
Complete a primeira linha de um script em C#:
4. Você rodou um programa com aviso duas vezes, sem mudar nada. Na segunda vez, o aviso não apareceu. Por quê?
5. O dotnet run terminou com o código de saída 134. O que aconteceu?
6. Onde o dotnet run ola.cs guarda o programa compilado?
7. Numa pasta com um .csproj, o dotnet run ola.cs deu o erro CS8802. Por quê?
Missão final: o programa que cumprimenta
Missão: a caixa roda o programa como dotnet run ola.cs Ana. Faça o programa cumprimentar quem veio no primeiro argumento, args[0] (a contagem começa do zero): troque a linha por Console.WriteLine($"Olá, {args[0]}!"); e rode. A saída deve ser Olá, Ana!.
Console.WriteLine("Olá, mundo!");
Console.WriteLine($"Olá, {args[0]}!");
No seu terminal, rode o mesmo arquivo com dotnet run ola.cs Bia e com dotnet run ola.cs, sem argumento. O segundo dá uma exceção, com o código 134: sem argumento, não existe args[0]. A aula Argumentos da Linha de Comando ensina a conferir antes.
Resumo
- O .NET 10 roda um arquivo
.cssozinho, sem projeto:dotnet run ola.cs(oudotnet ola.cs). - Ele compila na primeira vez (uns segundos) e guarda o programa em
~/.local/share/dotnet/runfile. Se o arquivo não mudou, só roda (menos de meio segundo). - Os avisos só aparecem quando ele compila; para ver de novo,
--no-cache. - Os argumentos vêm depois do arquivo e chegam no
args; use--antes deles para odotnetnão roubar nenhum. - Códigos de saída:
0(deu certo),1(não compilou), o doreturn,134(exceção não tratada). - No topo do arquivo,
#:packageusa pacotes NuGet e#:propertymuda configurações. Com#!/usr/bin/env dotnetechmod +x, o arquivo vira script. - Não misture arquivos soltos com pastas de projeto (
CS8802). Quando o programa crescer,dotnet project convert. - Próximo passo: Projetos (dotnet new console).