Running a command from a string with parameters in go

Viewed 2497

I am trying to run a command with go. The command is in a string.

package main

import (
    "log"
    "os"
    "os/exec"
    "strings"

    "github.com/davecgh/go-spew/spew"
)

func main() {
    commandToRun := `echo $HOME`

    log.Printf("Running %s\n", commandToRun)

    args := strings.Fields(commandToRun)
    spew.Dump(args[1:len(args)])
    command := exec.Command(args[0], args[1:len(args)]...)
    command.Stdout = os.Stdout
    command.Stdin = os.Stdin
    command.Stderr = os.Stderr
    err := command.Run()

    if err != nil {
        log.Printf("Command finished with error: %v", err)
    }
}

The output is:

2018/11/14 09:41:22 Running echo $HOME
([]string) (len=1 cap=1) {
 (string) (len=5) "$HOME"
}
$HOME

What I'd like to have is:

2018/11/14 09:41:22 Running echo $HOME
([]string) (len=1 cap=1) {
 (string) (len=5) "$HOME"
}
/home/whatever

Looks like go is sanitizing the string somehow. So the $HOME is not expanded. Is there any way of running the string exactly as if it was typed into the shell?

This is the important part. Ideally I'd like to turn from string to type in the current shell.

EDIT: The example below solve the simplest scenario but doesn't cover the "running the string exactly as if it was typed into the shell" part.

If I switch to expandenv:

commandToRun := os.ExpandEnv(`echo "$HOME"`)

I get:

2018/11/14 11:45:44 Running echo "/Users/rafael"
([]string) (len=1 cap=1) {
 (string) (len=15) "\"/home/whatever\""
}
"/home/whatever"

What I'd get in the shell is:

$ > echo "$HOME"
/home/whatever

without the quotes.

This is close to what I want but not exactly it.

3 Answers

$HOME (and all other env variables) are expanded by the shell. You're not executing a shell, so they don't get expanded.

You need to look up the env variable directly in go, with something like:

command := exec.Command("echo", os.Getenv("HOME"))

or this:

commandToRun := os.ExpandEnv("echo $HOME")
command := exec.Command(strings.Fields(commandToRun)...)

Note that this last approach won't work if $HOME expands to a string containing whitespace, so the os.Getenv method is generally safer/preferred for this use case.

Before executing the command, you can actively expand all env vars in the string using os.ExpandEnv:

os.ExpandEnv("echo $HOME")

From the docs:

ExpandEnv replaces ${var} or $var in the string according to the values of the current environment variables. References to undefined variables are replaced by the empty string.

If you want to get the output of $ echo $HOME, the minimal code you need is

fmt.Println(os.Getenv("HOME"))

Nothing more is needed.

  • If you use os.ExpandEnv("echo $HOME"), then first $HOME var will be expanded and then it will give you a string like echo /home/<user>
  • If you use command := exec.Command("echo", os.Getenv("HOME")), then it will be resolved as command := exec.Command("echo", "/home/<user>") and finally which will give output /home/<user>
  • If you use

    commandToRun := os.ExpandEnv("echo $HOME") command := exec.Command(strings.Fields(commandToRun)...)

    then it will be process like previous cases.

So better way is using only fmt.Println(os.Getenv("HOME")).

Related