11

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 !

3
  • 2
    You can't. "Sourcing" is only possible within the shell process. Commented May 1, 2015 at 22:11
  • BTW, please don't do anything like this in "real" code. At least not without massive warnings. Shell scripts can of course have arbitrary side effects, I can't think of any easy safe way to do this with a shell script but there are easy safe alternatives that cover almost all the use cases I can think of. Commented May 2, 2015 at 14:45
  • Thanks for your warning and concerns. I should have explain what I want to do. I'm trying to initialize my shell with a Go program. So I have to copy/link some dotfiles and source some others. That's why side effects are expected in my case. Commented May 2, 2015 at 16:02

2 Answers 2

10

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"))
Sign up to request clarification or add additional context in comments.

3 Comments

Your sample works well for updating the environment of the run command, but it doesn't affect the current execution context of the Go program. See: beta.42grounds.io/s/…
@Pith, use play.golang.org for linking to Go code, your link is completely unreadable. E.g. play.golang.org/p/asAcntp-j8
I don't understand why you find it unreadable. But why I didn't use the Go playground, is because the script doesn't run in the Go playground. Unlike the link I posted which run the script in a container.
3

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.

Comments

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.