os.Args
os.Args é um slice de strings. os.Args[0] é o nome do programa, e o resto são os argumentos exatamente como o shell os passou.
No painel Args do editor, cada campo é um argumento, passado como está. Experimente hello, two words e -v em três campos: o programa vê três argumentos, e two words continua sendo um argumento só, com um espaço dentro. Em um terminal quem faz a divisão é o shell, então o equivalente é go run . hello "two words" -v.
Sempre verifique len(os.Args) antes de indexar. os.Args[1] sem argumentos causa panic com index out of range [1] with length 1.
Argumentos são strings. Converta números com strconv.Atoi ou strconv.ParseFloat e trate o erro, já que usuários digitam qualquer coisa.
O pacote flag
Para opções como -port 8080 -verbose, use o flag. Ele interpreta, converte tipos, reporta erros e gera uma mensagem de ajuda.
Sem argumentos, isto imprime hello, world uma vez. No painel Args, experimente -name e Gopher em dois campos, e depois -count=3, -loud e extra em mais três. O programa imprime HELLO, GOPHER! três vezes e remaining args: [extra].
Como as flags funcionam:
- Cada função de definição (
flag.String,flag.Int,flag.Bool,flag.Float64,flag.Duration,flag.Uint64...) recebe um nome, um valor padrão e um texto de ajuda, e devolve um ponteiro. Leia o valor com*nomedepois deflag.Parse(). - As formas
Varse ligam a uma variável que você já tem:flag.IntVar(&cfg.Port, "port", 8080, "port"). Isso fica mais organizado com uma struct de configuração. - Os usuários podem escrever
-name value,-name=value,--name valueou--name=value. O Go não faz diferença entre um e dois hífens. - Booleanos precisam de
=para receber um valor.-louddefine true,-loud=falsedefine false, mas-loud falsedefine true e deixafalsecomo argumento posicional. - A interpretação para no primeiro argumento que não é flag (ou em
--).prog file.txt -vtrata-vcomo argumento posicional. Coloque as flags primeiro. flag.Args()devolve os argumentos posicionais que sobraram,flag.NArg()a quantidade deles eflag.Arg(i)um deles.
Uma flag desconhecida ou um valor inválido imprime um erro mais a ajuda e sai com status 2. -h ou -help imprime a ajuda e sai com status 0 (desde o Go 1.15). O texto de ajuda é gerado a partir das suas definições:
Usage of greet:
-count int
how many times (default 1)
-delay duration
pause between greetings, e.g. 10ms
-loud
shout the greeting
-name string
who to greet (default "world")
Defina flag.Usage como uma função para imprimir o seu próprio cabeçalho antes de chamar flag.PrintDefaults().
Subcomandos com FlagSet
Ferramentas como git commit -m msg têm subcomandos com flags próprias. Crie um flag.FlagSet por subcomando e escolha um com um switch sobre o primeiro argumento:
flag.ContinueOnError faz o Parse devolver um erro em vez de sair, o que mantém a função testável. run recebe os argumentos como parâmetro em vez de ler os.Args, então um teste pode chamar run([]string{"list", "-all"}) diretamente. Experimente list e -all no painel Args, ou delete para ver o caminho de erro.
Para CLIs grandes com comandos aninhados, completação no shell e documentação gerada, a maioria dos projetos usa o pacote de terceiros github.com/spf13/cobra. O pacote padrão flag atende bem ferramentas pequenas.
Variáveis de ambiente
os.Getenvdevolve""tanto quando a variável não existe quanto quando ela está definida como string vazia. Oos.LookupEnvdiferencia os dois casos.- Os valores são sempre strings. Converta-os e valide-os na inicialização, e falhe com uma mensagem clara em vez de no meio de uma requisição.
os.Setenvafeta o processo atual e os processos filhos iniciados depois. Ele não consegue mudar o ambiente do shell que iniciou o seu programa.os.Environ()devolve todas as variáveis como strings"KEY=value".
Uma organização comum para configuração: flags para o que uma pessoa digita, variáveis de ambiente para configurações de deploy (plataformas de containers as definem), com as flags sobrescrevendo o ambiente e o ambiente sobrescrevendo os padrões.
Códigos de saída e os.Exit
Um programa Go sai com status 0 quando main retorna. os.Exit(code) encerra o processo na hora com esse status. Por convenção, 0 é sucesso, 1 é um erro geral e 2 é um erro de uso (o pacote flag usa 2).
os.Exit não executa as funções adiadas. Os arquivos não são gravados por completo e a limpeza com defer é pulada. log.Fatal chama os.Exit(1) e tem o mesmo efeito. Mantenha o os.Exit em um único lugar, no fim do main, e coloque o programa de verdade em uma função run que devolve um erro, como faz o exemplo de subcomandos:
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
Escreva as mensagens de erro em os.Stderr, não em os.Stdout, para que continuem visíveis quando a saída for redirecionada para um arquivo ou encadeada com outro comando. Um panic não recuperado sai com status 2.
Erros comuns
- Indexar
os.Argssem verificar o tamanho. Argumentos faltando causam panic. - Ler flags antes de
flag.Parse(). Você recebe os valores padrão. - Esquecer o
*.fmt.Println(port)imprime um endereço como0xc000012345, não o valor. - Colocar flags depois de argumentos posicionais.
prog input.txt -vnão interpreta o-v. -verbose falsepara uma flag bool. Escreva-verbose=false.- Chamar
os.Exitoulog.Fatalno fundo do programa. A limpeza adiada nunca executa e o código não pode ser testado. Devolva os erros até omain.
Perguntas frequentes
Como pegar os argumentos de linha de comando em Go?
os.Args é um []string com o nome do programa no índice 0 e os argumentos depois dele. os.Args[1:] são os argumentos que o usuário digitou. Verifique len(os.Args) antes de indexar, ou o programa causa panic quando falta um argumento.
Como usar o pacote flag em Go?
Declare as flags, chame flag.Parse() e depois leia os valores: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). As funções devolvem ponteiros. Os usuários escrevem -port=9000, -port 9000 ou --port 9000, e -h imprime a ajuda gerada.
Como ler uma variável de ambiente em Go?
os.Getenv("HOME") devolve o valor, ou uma string vazia se a variável não estiver definida. Para diferenciar não definida de definida como vazia, use v, ok := os.LookupEnv("HOME"). os.Setenv altera o ambiente do processo atual e dos processos filhos que ele iniciar depois.
O os.Exit executa as funções adiadas em Go?
Não. O os.Exit encerra o processo na hora com o código de status informado, e as chamadas adiadas não executam, então saída em buffer pode se perder e arquivos podem não ser gravados por completo. Um padrão comum é func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, com todo o trabalho real e os defers dentro de run.