I am in a situation where I am trying to execute a shell command, but have its arguments be interpreted as environment variables properly.
For example, when I type the following into the terminal
ls $GOPATH
Bash interprets and expands the variable $GOPATH, and lists the contents of the $GOPATH directory. I am trying to do a similar thing with Golang's programmatic shell execution.
I have the following code.
package main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("echo", "$TESTVAR")
cmd.Env = append(os.Environ(),
"TESTVAR=this_is_a_test",
)
var outBuff bytes.Buffer
var errBuff bytes.Buffer
cmd.Stdout = &outBuff
cmd.Stderr = &errBuff
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
fmt.Println(outBuff.String()) // empty
fmt.Println(errBuff.String()) // empty
}
This program outputs
$ go run test.go
$TESTVAR
Does anyone have any idea how to make the exec library interpret $TESTVAR as an environment variable as opposed to a string literal? Thanks in advance!
Replace
cmd := exec.Command("echo", "$TESTVAR")
with
cmd := exec.Command("sh", "-c", "echo $TESTVAR")
Bash and other shells interprets and expands variables, but the application is not executing Bash.
The application is executing the echo command. The echo command, like most other commands, does not expand environment variables in its arguments.
You can either run Bash as shown in another answer or expand environment variables on your own. Here's how to use os.Expand function to do this:
func newCommandEnv(env []string, cmd string, args ...string) *exec.Cmd {
m := map[string]string{}
for _, e := range env {
if i := strings.Index(e, "="); i >= 0 {
m[e[:i]] = e[i+1:]
}
}
fn := func(placeholder string) string {
return m[placeholder]
}
for i, a := range args {
args[i] = os.Expand(a, fn)
}
fmt.Println(args)
c := exec.Command(cmd, args...)
c.Env = env
return c
}
Related
I'm trying to call appcmd from within Go. The code below shows success, but the password is set to the wrong thing. If I remove the inner quotes (on the second line of main) it works, but then it doesn't work when the password includes spaces! Now WITH the quotes, if I type in cmd.exe the command exactly as it outputs, it works! So what the heck! Why does it work with the quotes directly in cmd but not when called from Go?
I really don't want to be that guy who says you can't use spaces in passwords because I can't figure out why it doesn't work! UGH!
package main
import (
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
"syscall"
)
func main() {
iisPath := "C:\\WINDOWS\\sysWOW64\\inetsrv\\"
callAppcmd(iisPath, "-processModel.password:\"password\"")
}
func callAppcmd(iisPath string, param string) {
stdOut, _, _, exitCode := runCommand(
iisPath+"appcmd.exe",
"set",
"apppool",
"/apppool.name:DefaultAppPool",
param)
printOut(stdOut)
printOut(strconv.Itoa(exitCode))
}
func printOut(text string) {
fmt.Println(text)
}
func runCommand(commands ...string) (string, string, error, int) {
printOut(strings.Join(commands, " "))
cmd := exec.Command(commands[0], commands[1:]...)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
exitCode := 0
if exitError, ok := err.(*exec.ExitError); ok {
exitCode = exitError.ExitCode()
}
return out.String(), stderr.String(), err, exitCode
}
Output:
C:\WINDOWS\sysWOW64\inetsrv\appcmd.exe set apppool /apppool.name:DefaultAppPool -processModel.password:"password"
APPPOOL object "DefaultAppPool" changed
0
It seems to format the string with backticks is a solution to this, which will not do automatic escaping and can process the quotes properly.
cmd := exec.Command(`find`)
cmd.SysProcAttr.CmdLine = `find "SomeText" test.txt`
Please refer to the below link.
exec with double quoted argument
I want to source shell scripts using Go. Ideally the following code
cmd := exec.Command("/bin/bash", "source", file.Name())
but, I know that "source" is a bash built-in function, not an executable.
However, I have found some ways to mimic this behavior in Python:
http://pythonwise.blogspot.fr/2010/04/sourcing-shell-script.html
Unfortunately, I don't know how to translate this in Go. Does anyone have an idea ?
Thanks !
You can set environmental variables when running a program using exec:
cmd := exec.Command("whatever")
cmd.Env = []string{"A=B"}
cmd.Run()
If you really need source then you can run your command through bash:
cmd := exec.Command("bash", "-c", "source " + file.Name() + " ; echo 'hi'")
cmd.Run()
Check out this library for a more full-featured workflow: https://github.com/progrium/go-basher.
Update: Here's an example that modifies the current environment:
package main
import (
"bufio"
"bytes"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
)
func main() {
err := ioutil.WriteFile("example_source", []byte("export FOO=bar; echo $FOO"), 0777)
if err != nil {
log.Fatal(err)
}
cmd := exec.Command("bash", "-c", "source example_source ; echo '<<<ENVIRONMENT>>>' ; env")
bs, err := cmd.CombinedOutput()
if err != nil {
log.Fatalln(err)
}
s := bufio.NewScanner(bytes.NewReader(bs))
start := false
for s.Scan() {
if s.Text() == "<<<ENVIRONMENT>>>" {
start = true
} else if start {
kv := strings.SplitN(s.Text(), "=", 2)
if len(kv) == 2 {
os.Setenv(kv[0], kv[1])
}
}
}
}
log.Println(os.Getenv("FOO"))
I have recently added such a utility function to my shell/bash Golang library:
https://godoc.org/mvdan.cc/sh/shell#SourceFile
For example, you could do:
vars, err := shell.SourceFile("foo.sh")
if err != nil { ... }
fmt.Println(vars["URL"].Value)
// http://the.url/value
It's decently safe, because it never actually calls bash nor any other program. It parses the shell script, then interprets it. But when interpreting, it has a whitelist of what files the script can open and what programs the script can execute.
The interpreter also has a context.Context, so you can set a timeout if you want to be protected against forever loops or other bad code.
Given the following:
import(
"bytes"
"code.google.com/p/go/src/pkg/text/template"
)
....
var tmp = template.Must(template.New("").Parse(`
echo {{.Name}}
echo {{.Surname}}
`[1:]))
var buf bytes.Buffer
tmp.Execute(&buf, struct{Name string, Surname: string}{"James","Dean"})
bashScript = string(buf)
// Now, how do I execute the bash script?
magic.Execute(bashScript)
Is there a magic function that will execute the string as one bash script? "os/exec".Command can execute only one command at a time.
If you want to execute more than one command, especially more than one at a time, bash is not the best way to do that. Use os/exec and goroutines.
If you really want to run a bash script, here's an example using os/exec. I assumed you wanted to see the output of the bash script, rather than save it and process it (but you can easily do that with a bytes.Buffer). I've removed all the error checking here for brevity. The full version with error checking is here.
package main
import (
"bytes"
"io"
"text/template"
"os"
"os/exec"
"sync"
)
func main() {
var tmp = template.Must(template.New("").Parse(`
echo {{.Name}}
echo {{.Surname}}
`[1:]))
var script bytes.Buffer
tmp.Execute(&script, struct {
Name string
Surname string
}{"James", "Dean"})
bash := exec.Command("bash")
stdin, _ := bash.StdinPipe()
stdout, _ := bash.StdoutPipe()
stderr, _ := bash.StderrPipe()
wait := sync.WaitGroup{}
wait.Add(3)
go func () {
io.Copy(stdin, &script)
stdin.Close()
wait.Done()
}()
go func () {
io.Copy(os.Stdout, stdout)
wait.Done()
}()
go func () {
io.Copy(os.Stderr, stderr)
wait.Done()
}()
bash.Start()
wait.Wait()
bash.Wait()
}
Use bash -c... exec.Command("bash", "-c", bashScript).
I'm writing a small program with an interpreter, I would like to pipe any command that is not recognized by my shell to bash, and print the output as if written in a normal terminal.
func RunExtern(c *shell.Cmd) (string, os.Error) {
cmd := exec.Command(c.Cmd(), c.Args()...)
out, err := cmd.Output()
return string(out), err
}
this is what I've written so far, but it only executes a program with its args, I would like to send the whole line to bash and get the output, any idea how to do so ?
For example, to list directory entries in columns,
package main
import (
"exec"
"fmt"
"os"
)
func BashExec(argv []string) (string, os.Error) {
cmdarg := ""
for _, arg := range argv {
cmdarg += `"` + arg + `" `
}
cmd := exec.Command("bash", "-c", cmdarg)
out, err := cmd.Output()
return string(out), err
}
func main() {
out, err := BashExec([]string{`ls`, `-C`})
if err != nil {
fmt.Println(err)
}
fmt.Println(out)
}
Isn't this Golang program supposed to output a directory listing to stdout?
It compiles ok, but does nothing.
package main
import "exec"
func main() {
argv := []string{"-la"}
envv := []string{}
exec.Run("ls", argv, envv, "", exec.DevNull, exec.PassThrough, exec.MergeWithStdout)
}
this works:
package main
import "exec"
func main() {
cmd, err := exec.Run("/bin/ls", []string{"/bin/ls", "-la"}, []string{}, "", exec.DevNull, exec.PassThrough, exec.PassThrough)
if (err != nil) {
return
}
cmd.Close()
}
You could also do it in native go using: ioutil.ReadDir(dir), like so:
//listdir.go
package main
import (
"os"
"io/ioutil"
"fmt"
)
func ListDir(dir string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dir)
}
func main() {
dir := "./"
if len(os.Args) > 1 {
dir = os.Args[1]
}
fi, err := ListDir(dir)
if err != nil {
fmt.Println("Error", err)
}
for _, f := range fi {
d := "-"
if f.IsDir() { d = "d" }
fmt.Printf("%s %o %d %s %s\n", d, f.Mode() & 0777, f.Size(), f.ModTime().Format("Jan 2 15:04"), f.Name())
}
}
Checkout the documentation available for ioutil and os packages.
By default exec.Command will leave standard input, output and error connected to /dev/null. So, your 'ls' command is running fine but the output is just being thrown away. If you add:
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
before the exec.Run call then your output will go where you probably expect it.
exec.Run replaces your program with the one it executes -- it never returns to your app. This means that when 'cd' completes, it will exit as normal, and the only effect should be of changing the directory; 'ls' will never run.