Captura de Saída Padrão e Erros
A biblioteca padrão os/exec oferece métodos diretos para capturar o retorno de processos externos. Utilize CombinedOutput() quando precisar mesclar stdout e stderr em um único fluxo, ou Output() para isolar apenas a saída padrão.
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
// Mescla stdout e stderr
proc := exec.Command("hostname")
resultado, erroExec := proc.CombinedOutput()
if erroExec != nil {
log.Fatalf("falha na execução: %v", erroExec)
}
fmt.Printf("Saída combinada: %s\n", resultado)
// Retorna exclusivamente stdout
procSistema := exec.Command("uptime")
saidaLimpa, erroSistema := procSistema.Output()
if erroSistema != nil {
log.Fatal(erroSistema)
}
fmt.Printf("Uptime: %s", saidaLimpa)
}
Isolamento de Streams com Buffers
Para tratar stdout e stderr de forma independente, atribua instânciass de bytes.Buffer aos campos correspondentes do comando. Caso deseje espelhar a saída diretamente no terminal do usuário, basta apontar para os.Stdout e os.Stderr.
var bufSaida, bufErro bytes.Buffer
tarefa := exec.Command("df", "-h")
tarefa.Stdout = &bufSaida
tarefa.Stderr = &bufErro
if erro := tarefa.Run(); erro != nil {
log.Printf("Erro ao executar: %v", erro)
}
fmt.Println("STDOUT:", bufSaida.String())
fmt.Println("STDERR:", bufErro.String())
// Redirecionamento direto para o console
tarefaConsole := exec.Command("df", "-h")
tarefaConsole.Stdout = os.Stdout
tarefaConsole.Stderr = os.Stderr
tarefaConsole.Run()
Execução Assíncrona e Concorrente
O método Run() é bloqueante. Para iniciar processos em segundo plano e capturar fluxos simultaneamente, utilize Start() combinado com Wait() e goroutines. Essa abordagem é ideal para comandos de longa duração ou que geram saída contínua.
var cacheOut, cacheErr bytes.Buffer
processo := exec.Command("ping", "-c", "3", "127.0.0.1")
pipeOut, _ := processo.StdoutPipe()
pipeErr, _ := processo.StderrPipe()
if erro := processo.Start(); erro != nil {
log.Fatalf("Início falhou: %v", erro)
}
go io.Copy(io.MultiWriter(os.Stdout, &cacheOut), pipeOut)
go io.Copy(io.MultiWriter(os.Stderr, &cacheErr), pipeErr)
if erro := processo.Wait(); erro != nil {
log.Fatalf("Processo finalizado com erro: %v", erro)
}
Injeção de Variáveis de Ambiente
Por padrão, o processo herda o ambiente do programa Go. Para sobrescrever ou adicionar variáveis específicas, modifique a slice cmd.Env. É recomendável partir de os.Environ() para manter variáveis críticas do sistema.
cmd := exec.Command("printenv", "MEU_APP_ENV")
ambienteBase := os.Environ()
ambienteCustom := append(ambienteBase, "MEU_APP_ENV=producao_v2")
cmd.Env = ambienteCustom
res, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Variável capturada: %s", res)
Verificação de Disponibilidade do Binário
Entes de invocar um executável, valide sua presença no $PATH utilizando exec.LookPath. Isso evita erros de execução em tempo de runtime e permite fallbacks elegantes.
func verificarBinario(nome string) bool {
caminho, err := exec.LookPath(nome)
if err != nil {
fmt.Printf("Binário '%s' não localizado no PATH.\n", nome)
return false
}
fmt.Printf("Encontrado em: %s\n", caminho)
return true
}
Encadeamento e Pipes
O exec.Command não interpreta operadores de shell como |, && ou >. Para encadear comandos, conecte manualmente o StdoutPipe de um processo ao Stdin do seguinte, ou delegue a interpretação para um shell.
// Método nativo com pipes manuais
origem := exec.Command("echo", "golang é eficiente")
destino := exec.Command("wc", "-w")
pipeLeitura, _ := origem.StdoutPipe()
destino.Stdin = pipeLeitura
destino.Stdout = os.Stdout
destino.Start()
origem.Run()
destino.Wait()
// Alternativa via interpretador shell
script := "ps aux | grep init | awk '{print $2}'"
saida, _ := exec.Command("sh", "-c", script).Output()
fmt.Println(saida)
Leitura Linha a Linha
Para processar saídas extenass sem carregar tudo na memória, utilize bufio.Scanner conectado ao pipe de saída. Essa abordagem é mais segura e idiomática que leituras baseadas em delimitadores manuais.
proc := exec.Command("seq", "1", "5")
stream, _ := proc.StdoutPipe()
proc.Start()
scanner := bufio.NewScanner(stream)
for scanner.Scan() {
linha := strings.TrimSpace(scanner.Text())
log.Printf("Linha processada: %s", linha)
}
proc.Wait()
Tratamento de Códigos de Saída
Quando um comando falha, o erro retornado pode ser convertido para *exec.ExitError. Versões modernas do Go expõem diretamente o código de retorno via método ExitCode(), eliminando a necessidade de conversões manuais com syscall.WaitStatus.
func executarComRetorno(nome string, args ...string) (int, string) {
var buffer bytes.Buffer
c := exec.Command(nome, args...)
c.Stdout = &buffer
c.Stderr = &buffer
err := c.Run()
saida := buffer.String()
if err != nil {
if erroSaida, ok := err.(*exec.ExitError); ok {
return erroSaida.ExitCode(), saida
}
return -1, fmt.Sprintf("erro de sistema: %v", err)
}
return 0, saida
}