Golang: execute text/template as bash script - go

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

Related

Send stdout of running command to its stdin in go

I have a somewhat challenging situation where I need to write into a system command stdin the same stdout it outputs (in another running program), here's an example program that represents what I mean:
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
)
func main() {
rand.Seed(time.Now().Unix())
var greetings []string = []string{"hi", "hola", "bonjour", "hallo", "whats up"}
var greeting string = greetings[rand.Intn(len(greetings))]
fmt.Println(greeting)
reader := bufio.NewReader(os.Stdin)
message, _ := reader.ReadString('\n')
if message == greeting+"\n" {
fmt.Println("nice to meet you!")
} else {
fmt.Println("oops!")
}
}
Since you greet with a random greeting, you have to read the stdout, send it to stdin and also capture if it was the correct answer or not. I've tried with stdinpipes but it freezes waiting for the stdin close since I think that only works for the start of the command run only, so for a running program it hasn't been working for me...
I appreciate any help!
EDIT
I wanted to add sort of what I was trying to do, I've tried without channels as well but it didn't seem to make a difference on the outcome, it just freezes waiting for stdin to close and I need to get first stdout before closing stdin since it consists of it:
package main
import (
"io"
"os/exec"
)
func main() {
cmd := exec.Command("./executable_program")
stdout, _ := cmd.StdoutPipe()
stdin, _ := cmd.StdinPipe()
var c chan []byte = make(chan []byte)
cmd.Start()
go func() {
b, _ := io.ReadAll(stdout)
c <- b
}()
stdin.Write(<-c)
stdin.Close()
cmd.Wait()
}
You can use a pipe to join the stdout to the stdin of the program that you execute:
package main
import (
"io"
"os/exec"
)
func main() {
r, w := io.Pipe()
cmd := exec.Command("<name-of-program-to-run>")
cmd.Stdin = r
cmd.Stdout = w
cmd.Run()
}
To see this in action, first let's prepare a test program to be executed by the program above. This test program simply prints a line to stdout, and then reads each line of stdin and prints it to stdout until stdin is closed.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fmt.Fprint(os.Stdout, "priming the pump!\n")
s := bufio.NewScanner(os.Stdin)
for s.Scan() {
line := s.Text()
fmt.Fprint(os.Stdout, line+"\n")
}
}
Then, we modify our initial program to print the bytes traversing through the pipe so we see what's going on.
package main
import (
"fmt"
"io"
"os/exec"
)
func main() {
r, w := io.Pipe()
sr := &readSpy{r: r}
wr := &writeSpy{w: w}
cmd := exec.Command("./test-program")
cmd.Stdin = sr
cmd.Stdout = wr
cmd.Run()
}
type readSpy struct {
r io.Reader
}
func (s *readSpy) Read(d []byte) (int, error) {
size, err := s.r.Read(d)
fmt.Println("readSpy read", string(d[:size]))
return size, err
}
type writeSpy struct {
w io.Writer
}
func (s *writeSpy) Write(d []byte) (int, error) {
size, err := s.w.Write(d)
fmt.Println("writeSpy wrote", string(d[:size]))
return size, err
}
Running the above, you will see the following getting printed in a infinite loop, which makes sense since the priming the pump! string is printed to stdout and fed right back to the stdin of the test program:
writeSpy wrote priming the pump!
readSpy read priming the pump!
...repeated forever...

Run program w/ exec.Command redirecting stdout to /dev/null

I am translating my script from bash to go and unfortunately I am not able to make pass command work. When I run the bash one I receive a window to provide my password to the pass manager.
Here is part of my go code:
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
)
func run_command(command *exec.Cmd) string {
attach_json, err := command.Output()
if err != nil {
fmt.Println(err.Error())
// os.Exit(0)
}
fmt.Println(attach_json)
return string(attach_json)
}
email := "xyz#abc.com"
cmd_emamil := "GoJira/api-token:" + email
pass_cmd := exec.Command("pass", cmd_emamil, "> /dev/null")
pass_cmd := exec.Command("pass", cmd_emamil)
run_command(pass_cmd)
In the shell command pass >/dev/null, >/dev/null is not an argument to pass; instead, it's an instruction to the shell, telling it to replace file descriptor 1 (stdout) with a handle on /dev/null before pass is started.
When you use exec.Command() there is no shell, so you can't use shell syntax. Instead, assign the file you want stdout to be redirected to to the Stdout of your exec.Command.
devnull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0755)
if err != nil { panic(err) }
cmd_email := "GoJira/api-token:" + email
pass_cmd := exec.Command("pass", cmd_email)
pass_cmd.Stdout = devnull

Calling appcmd results in the wrong password being set

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

Embed environment variables in programmatic shell command execution

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
}

Go exec.Command() - run command which contains pipe

The following works and prints the command output:
out, err := exec.Command("ps", "cax").Output()
but this one fails (with exit status 1):
out, err := exec.Command("ps", "cax | grep myapp").Output()
Any suggestions?
Passing everything to bash works, but here's a more idiomatic way of doing it.
package main
import (
"fmt"
"os/exec"
)
func main() {
grep := exec.Command("grep", "redis")
ps := exec.Command("ps", "cax")
// Get ps's stdout and attach it to grep's stdin.
pipe, _ := ps.StdoutPipe()
defer pipe.Close()
grep.Stdin = pipe
// Run ps first.
ps.Start()
// Run and get the output of grep.
res, _ := grep.Output()
fmt.Println(string(res))
}
You could do:
out, err := exec.Command("bash", "-c", "ps cax | grep myapp").Output()
In this specific case, you don't really need a pipe, a Go can grep as well:
package main
import (
"bufio"
"bytes"
"os/exec"
"strings"
)
func main() {
c, b := exec.Command("go", "env"), new(bytes.Buffer)
c.Stdout = b
c.Run()
s := bufio.NewScanner(b)
for s.Scan() {
if strings.Contains(s.Text(), "CACHE") {
println(s.Text())
}
}
}

Resources