Menu

Argumentos de linha de comando em Golang: os.Args, flag e variáveis de ambiente

Como um programa Go lê a sua linha de comando: os.Args, o pacote flag para opções tipadas, subcomandos com FlagSet, variáveis de ambiente com os.Getenv e os.LookupEnv e códigos de saída com os.Exit.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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 *nome depois de flag.Parse().
  • As formas Var se 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 value ou --name=value. O Go não faz diferença entre um e dois hífens.
  • Booleanos precisam de = para receber um valor. -loud define true, -loud=false define false, mas -loud false define true e deixa false como argumento posicional.
  • A interpretação para no primeiro argumento que não é flag (ou em --). prog file.txt -v trata -v como argumento posicional. Coloque as flags primeiro.
  • flag.Args() devolve os argumentos posicionais que sobraram, flag.NArg() a quantidade deles e flag.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.Getenv devolve "" tanto quando a variável não existe quanto quando ela está definida como string vazia. O os.LookupEnv diferencia 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.Setenv afeta 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.Args sem 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 como 0xc000012345, não o valor.
  • Colocar flags depois de argumentos posicionais. prog input.txt -v não interpreta o -v.
  • -verbose false para uma flag bool. Escreva -verbose=false.
  • Chamar os.Exit ou log.Fatal no fundo do programa. A limpeza adiada nunca executa e o código não pode ser testado. Devolva os erros até o main.

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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR