Aceitando Argumentos em Linha de Comando
Vamos criar um novo projeto usando, como sempre, cargo new
. Chamaremos o nosso projeto
minigrep
para distingui-lo da ferramenta grep
que você já pode ter
no seu sistema.
$ cargo new --bin minigrep
Created binary (application) `minigrep` project
$ cd minigrep
A primeira tarefa é fazer que minigrep
aceite seus dois argumentos de linha de comando: o
nome de arquivo e uma string para procurar. Ou seja, queremos ser capazes de administrar o nosso
programa com cargo run
, uma string para procurar e um caminho para um arquivo onde será feira a
procura, dessa forma:
$ cargo run searchstring example-filename.txt
Neste momento, o programa gerado por cargo new
não pode processar os argumentos que nós
passamos. No entanto, algumas bibliotecas existentes no Crates.io
que podem nos ajudar a escrever um programa que aceite argumentos na linha de comando, mas
como você está aprendendo esses conceitos, vamos implementar essa capacidade
nós mesmos.
Lendo os Valores do Argumento
Para garantir que minigrep
seja capaz de ler os valores dos argumentos da linha de comando, nós
precisamos de uma função fornecida na biblioteca padrão do Rust, que é
std::env::args
. Esta função retorna um iterador da linha de comando
com os argumentos que foram passados à minigrep
. Ainda não discutimos iteradores
(nós os cobrimos totalmente no Capítulo 13), mas por enquanto você só precisa saber dois
detalhes sobre iteradores: os iteradores produzem uma série de valores, e podemos chamar
a função collect
em um iterador para transformá-lo em uma coleção, como um
vetor, contendo todos os elementos que o iterador produz.
Use o código na Listagem 12-1 para permitir que seu programa minigrep
leia qualquer
argumento da linha de comando passados para ele e depois colete os valores em um vetor:
Arquivo: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args); }
Primeiro, trazemos o módulo std::env
para o escopo com uma declaração use
, então nós
podemos usar a função args
. Observe que a função std::env::args
é
aninhada em dois níveis de módulos. Como discutimos no Capítulo 7, nos casos em que
a função desejada está aninhada em mais de um módulo, é convenção
trazer o módulo pai para o escopo em vez da função. Como resultado, nós
podemos facilmente usar outras funções de std::env
. Também é menos ambíguo que
adicionar use std::env::args
e depois chamando a função com apenas args
porque args
pode ser facilmente confundido com uma função definida no
módulo atual.
A Função
args
e Unicode InválidoNote que
std::env::args
emitirá pânico se algum argumento contiver código Unicode inválido. Se o seu programa precisar aceitar argumentos que sejam Unicode inválidos , usestd::env::args_os
em vez disso. Essa função retorna valoresOsString
em vez de valoresString
. Nós escolhemos usarstd::env::args
aqui por simplicidade, porque os valores deOsString
diferem por plataforma e são mais complexo para trabalhar do que os valores deString
.
Na primeira linha do main
, chamamos env::args
, e usamos collect
imediatamente
para transformar o iterador em um vetor contendo todos os valores produzidos pelo
iterador. Podemos usar a função collect
para criar muitos tipos de
coleções, então nós explicitamente anotamos o tipo de args
para especificar que nós
queremos um vetor de strings. Embora raramente precisemos anotar tipos em
Rust, collect
é uma função que muitas vezes você precisa anotar, porque Rust
não é capaz de inferir o tipo de coleção que deseja.
Finalmente, imprimimos o vetor usando o formatador de debug, :?
. Vamos tentar executar
o código sem argumentos e depois com dois argumentos:
$ cargo run
--snip--
["target/debug/minigrep"]
$ cargo run needle haystack
--snip--
["target/debug/minigrep", "needle", "haystack"]
Observe que o primeiro valor no vetor é "target/debug/minigrep"
, que
é o nome do nosso binário. Isso corresponde ao comportamento da lista de argumentos em
C, permitindo que os programas usem o nome pelo qual eles foram invocados em sua execução.
Geralmente, é conveniente ter acesso ao nome do programa, caso desejemos imprimi-lo em mensagens ou
alterar o comportamento do programa com base no alias da linha de comando que foi usado
para chamar o programa. Mas, para os fins deste capítulo, vamos ignorá-lo e salvar apenas
os dois argumentos que precisamos.
Salvando os Valores do Argumento em Variáveis
Imprimir o valor do vetor de argumentos ilustra que o programa é capaz de acessar os valores especificados como argumentos da linha de comando. Agora precisamos salvar os valores dos dois argumentos nas variáveis para que possamos usar esses valores durante o resto do programa. Fazemos isso na Listagem 12-2:
Arquivo: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); let query = &args[1]; let filename = &args[2]; println!("Searching for {}", query); println!("In file {}", filename); }
Como vimos quando imprimimos o vetor, o nome do programa ocupa o primeiro
valor no vetor em args[0]
, então estamos começando no índice 1
. O primeiro
argumento minigrep
é a string que estamos procurando, então colocamos uma
referência ao primeiro argumento na variável query
. O segundo argumento
será o nome do arquivo, então colocamos uma referência ao segundo argumento no
variável filename
.
Imprimimos temporariamente os valores dessas variáveis para provar que o código funciona
como pretendemos. Vamos executar este programa novamente com os argumentos test
e sample.txt
:
$ cargo run test sample.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt
Ótimo, o programa está funcionando! Os valores dos argumentos que precisamos estão sendo salvos nas variáveis certas. Mais tarde, adicionaremos algum tratamento de erro para lidar com certas situações errôneas potenciais, como quando o usuário não fornece argumentos; por enquanto, ignoraremos essa situação, e trabalharemos na adição das funcinalidades de leitura dos arquivos.