Since Go v1.16 there’s the embed package, a simple tool to embed files as filesystems of simple strings or []bytes.
While it has its downsides, I was recently met with a challenge. I’d like to run bash scripts I wrote because it’s more efficient to just run a bash script than breaking my fingers writing os/exec stuff.
Anyhow it’s pretty simple really. Any shell command has a standard input and output. You just assign the file pointer (aka the Reader) to the os.Command’s Stdin.
Example:
Imagine the following directory structure
1 2 3 4 5 6 7 |
├── go.mod ├── go.sum ├── install │ ├── bash │ │ └── test.bash │ └── install.go └── main.go |
bash/test.bash
1 2 3 |
#!/bin/bash NAME="Sunny" echo "Hello $NAME, this is a test" |
install.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package install import "embed" //go:embed bash/* var bash embed.FS func Run() error { f, e := bash.Open("bash/test.bash") if e != nil { return e } defer f.Close() cmd := exec.Command("bash") cmd.Stdin = f cmd.Stdout = os.Stdout if e := cmd.Run(); e != nil { return e } return nil } |
Output:
Hello Sunny, this is a test
Imagine the possibilities. You can now embed PHP, JS, all kinds of scripting languages in Go and execute those scripts without ever needing to write any of the embedded content to disk. Of course you need the interpreters installed, but if you have enough time on your hands maybe you could figure out how to embed a scripting language and virtually pipe a script to an interpreter.
And while that’s nice, I do have a problem. How do you pass arguments like this?
Thanks to Jon Lundy this problem is solved.
bash/hello.bash
1 2 3 4 5 6 |
#!/bin/bash echo "Hello there," for arg in "$@" do echo "$arg"; done |
and
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package main import ( "embed" "log" "os" "os/exec" ) //go:embed bash/* var bash embed.FS func main() { f, e := bash.Open("bash/hello.bash") if e != nil { log.Fatal(e.Error()) } defer f.Close() cmd := exec.Command("bash", "/dev/stdin", "Jon", "Lundy") cmd.Stdin = f cmd.Stdout = os.Stdout if e := cmd.Run(); e != nil { log.Fatal(e.Error()) } } |
Output:
Hello there,
Jon
Lundy
As an alternative you can also use
1 |
cmd := exec.Command("bash", "-s", "-", "one", "two") |
instead of
1 |
cmd := exec.Command("bash", "/dev/stdin", "one", "two") |
https://stackoverflow.com/a/8514318
cat script.sh | bash /dev/stdin arguments
so change exec.Command(“bash”, “/dev/stdin”, “arg1”, “arg2”)
Very nice. Thank you very much. 🙂
I’m editing the post.