6

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.

8
  • 2
    Looks like go is sanitizing the string somehow so the $HOME is not expanded. No, its not expanded because nobody expanded them. The shell does expansion, you're not invoking a shell, you're calling the command directly. Commented Nov 14, 2018 at 8:50
  • Ok, true, makes sense. But then I guess I cannot run the command in the current executing shell unless I save it to a script file or some dirty trick like that. Commented Nov 14, 2018 at 9:04
  • Is os.Getenv() of use to you? Commented Nov 14, 2018 at 9:19
  • 2
    BTW, exec.Command(args[0], args[1:len(args)]...) can be shortened to: exec.Command(args...) Commented Nov 14, 2018 at 9:25
  • 1
    And even if you couldn't do exec.Command(args...), the len part is not needed. If a is a slice or array, then the statement a[n:len(a)] is identical to the statement a[n:], just as a[0:n] is identical to a[:n] (and a[:] is equivalent to a[0:len(a)]) Commented Nov 14, 2018 at 18:20

4 Answers 4

5

$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")
args := strings.Fields(commandToRun)
command := exec.Command(args[0], args[1:]...)

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.

Sign up to request clarification or add additional context in comments.

Comments

3

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.

Comments

1

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")).

6 Comments

I would assume that echo $HOME is just an example, not the actual behavior the OP is aiming for.
If so, then the OP already know how to use exec.Command(). But he asked for help because in that way it is not giving the expected result. That's why I answered this way. Isn't it helpful?
I interpret the question broadly as "How do expand environment variables when execing a command".
You see, in that case my answer isn't appropriate.
Then it is a possible duplicate of stackoverflow.com/questions/51015569/…
|
0

You need to run a shell. Expanding this in Go seems silly and error prone since you have a shell that you can run.

commandToRun := `echo "$SHELL"`
cmd := exec.Command("/bin/sh", "-c", commandToRun)
command := exec.Command(args[0], args[1:len(args)]...)
command.Stdout = os.Stdout
command.Stdin = os.Stdin
command.Stderr = os.Stderr
err := command.Run()

This should work on any POSIX system. Kids today put a lot of bashisms in their command lines so you could switch it to run /bin/bash but just be aware that won't work on FreeBSD or some other systems (other BSDs and their derivatives at a start).

1 Comment

Nice. This actually answers the question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.