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.
Related
I'm trying to compile a go package using exec.Command. I was successful with the arguments being "go" and "build" as such below:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("go", "build").Output()
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
However, while trying to perform a "go build" with more arguments it seems that I'm doing something wrong? This is what my code looks like:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("set", "GOOS=js&&", "set", "GOARCH=wasm&&", "go", "build", "-o", "C:/Users/Daniel/Desktop/go-workspace/src/sandbox/other/wasm/assets/json.wasm", "kard").Output()
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
The output is exec: "set": executable file not found in %PATH%
The normal command I would perform for this in the command line would be set GOOS=js&& set GOARCH=wasm&& go build -o C:\Users\Daniel\Desktop\go-workspace\src\sandbox\other\wasm\assets\json.wasm kard.
I assume there is something I'm misunderstanding with using exec.Command? I really appreciate any support.
The application uses shell syntax to set the environment variables, but the exec package does not use a shell (unless the command that you are running is a shell).
Use the command environment to specify the environment variables for the exec'ed command.
The go build does not normally write to stdout, but it does write errors to stderr. Use CombinedOutput instead of Output to easily capture error text.
cmd := exec.Command("go", "build", "-o", "C:/Users/Daniel/Desktop/go-workspace/src/sandbox/other/wasm/assets/json.wasm", "kard")
cmd.Env = []string{"GOOS=js", "GOARCH=wasm"}
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("%v: %s\n", err, out)
}
I'm trying to execute a linux command and convert the output to an int. This is my current code:
package main
import (
"os/exec"
"os"
"strconv"
_"fmt"
"log"
"bytes"
)
func main(){
cmd := exec.Command("ulimit", "-n")
cmdOutput := &bytes.Buffer{}
cmd.Stdout = cmdOutput
err := cmd.Run()
if err != nil {
os.Stderr.WriteString(err.Error())
}
count, err := strconv.Atoi( string(cmdOutput.Bytes()) )
if err != nil {
log.Fatal(err)
}
if count <= 1024 {
log.Fatal("This machine is not good for working!!!")
}
}
This is my current error:
2018/10/12 14:37:27 exec: "ulimit -n": executable file not found in
$PATH
I don't understand what this error means and how I can fix it.
There is no ulimit program in linux that you can run.
ulimit is a builtin of a shell. So you need to run a shell and have the shell run its internal ulimit command.
cmd := exec.Command("/bin/sh", "-c" "ulimit -n")
you will also have to remove the newline from the output of the ulimit command, e.g.
count, err := strconv.Atoi( strings.Trim(string(cmdOutput.Bytes()),"\n"))
A better alternative is to retreive these limits via the syscall API in go, see How to set ulimit -n from a golang program? - the accepted answer first gets and prints the current limit.
I'm having trouble figuring out how to run multiple commands using the os/exec package. I've trolled the net and stackoverflow and haven't found anything that works for me case. Here's my source:
package main
import (
_ "bufio"
_ "bytes"
_ "errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
)
func main() {
ffmpegFolderName := "ffmpeg-2.8.4"
path, err := filepath.Abs("")
if err != nil {
fmt.Println("Error locating absulte file paths")
os.Exit(1)
}
folderPath := filepath.Join(path, ffmpegFolderName)
_, err2 := folderExists(folderPath)
if err2 != nil {
fmt.Println("The folder: %s either does not exist or is not in the same directory as make.go", folderPath)
os.Exit(1)
}
cd := exec.Command("cd", folderPath)
config := exec.Command("./configure", "--disable-yasm")
build := exec.Command("make")
cd_err := cd.Start()
if cd_err != nil {
log.Fatal(cd_err)
}
log.Printf("Waiting for command to finish...")
cd_err = cd.Wait()
log.Printf("Command finished with error: %v", cd_err)
start_err := config.Start()
if start_err != nil {
log.Fatal(start_err)
}
log.Printf("Waiting for command to finish...")
start_err = config.Wait()
log.Printf("Command finished with error: %v", start_err)
build_err := build.Start()
if build_err != nil {
log.Fatal(build_err)
}
log.Printf("Waiting for command to finish...")
build_err = build.Wait()
log.Printf("Command finished with error: %v", build_err)
}
func folderExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
I want to the command like I would from terminal. cd path; ./configure; make
So I need run each command in order and wait for the last command to finish before moving on. With my current version of the code it currently says that ./configure: no such file or directory I assume that is because cd path executes and in a new shell ./configure executes, instead of being in the same directory from the previous command. Any ideas?
UPDATE I solved the issue by changing the working directory and then executing the ./configure and make command
err = os.Chdir(folderPath)
if err != nil {
fmt.Println("File Path Could not be changed")
os.Exit(1)
}
Still now i'm curious to know if there is a way to execute commands in the same shell.
If you want to run multiple commands within a single shell instance, you will need to invoke the shell with something like this:
cmd := exec.Command("/bin/sh", "-c", "command1; command2; command3; ...")
err := cmd.Run()
This will get the shell to interpret the given commands. It will also let you execute shell builtins like cd. Note that this can be non-trivial to substitute in user data to these commands in a safe way.
If instead you just want to run a command in a particular directory, you can do that without the shell. You can set the current working directory to execute the command like so:
config := exec.Command("./configure", "--disable-yasm")
config.Dir = folderPath
build := exec.Command("make")
build.Dir = folderPath
... and continue on like you were before.
I have tried following the Go Docs in order to call a python script which just outputs "Hello" from GO, but have failed until now.
exec.Command("script.py")
or I've also tried calling a shell script which simply calls the python script, but also failed:
exec.Command("job.sh")
Any ideas how would I achieve this?
EDIT
I solved following the suggestion in the comments and adding the full path to exec.Command().
Did you try adding Run() or Output(), as in:
exec.Command("script.py").Run()
exec.Command("job.sh").Run()
You can see it used in "How to execute a simple Windows DOS command in Golang?" (for Windows, but the same idea applies for Unix)
c := exec.Command("job.sh")
if err := c.Run(); err != nil {
fmt.Println("Error: ", err)
}
Or, with Output() as in "Exec a shell command in Go":
cmd := exec.Command("job.sh")
out, err := cmd.Output()
if err != nil {
println(err.Error())
return
}
fmt.Println(string(out))
First of all do not forget to make your python script executable (permissions and #!/usr/local/bin/python at the beginning).
After this you can just run something similar to this (notice that it will report you errors and standard output).
package main
import (
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("script.py")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Println(cmd.Run())
}
Below worked for me on Windows 10
python := path.Clean(strings.Join([]string{os.Getenv("userprofile"), "Anaconda3", "python.exe"}, "/"))
script := "my_script.py"
cmd := exec.Command("cmd", python, script)
out, err := cmd.Output()
fmt.Println(string(out))
if err != nil {
log.Fatal(err)
}
How to run a simple Windows command?
This command:
exec.Command("del", "c:\\aaa.txt")
.. outputs this message:
del: executable file not found in %path%
What am I doing wrong?
I got the same error as you.
But dystroy is correct: You can't run del or any other command built into cmd because there is no del.exe file (or any other del-executable for that matter).
I got it to work with:
package main
import(
"fmt"
"os/exec"
)
func main(){
c := exec.Command("cmd", "/C", "del", "D:\\a.txt")
if err := c.Run(); err != nil {
fmt.Println("Error: ", err)
}
}
You need a Windows cmd to execute your dir command.
Try this :
cmd := exec.Command("cmd", "/C", "dir").Output()
(sorry, no Windows computer to check it right now)
Found another solution too. Create a batch file that contains the following: del c:\aaa.txt
Then call it like this:
exec.Command("c:\\del.bat").Run()
In case you need the output of cmd:
if c, err := exec.Command("cmd","/c","del","a.txt").CombinedOutput(); err != nil {
log.Fatal(err)
} else {
fmt.Printf("%s\n", c)
}
Ok let's see, according to the documentation, in windows, processes receive commands as a single line string and do some parsing of their own. Exec's Command function builds the command string by combining all arguments together using CommandLineToArgvW, that despite being the most common quoting algorithm doesn't work for every application. Applications like msiexec.exe and cmd.exe use an incompatible unquoting algorithm, hence the extra mile.
Heres a different example using powershell
package main
import (
"os/exec"
"fmt"
"log"
)
func main() {
out, err := exec.Command("powershell","remove-item","aaa.txt").Output()
if err != nil {
log.Fatal(err)
} else {
fmt.Printf("%s",out)
}
you can try use github.com/go-cmd/cmd module.
because golang can not use syscall by default.
example:
import (
"fmt"
"time"
"github.com/go-cmd/cmd"
)
func main() {
// Start a long-running process, capture stdout and stderr
findCmd := cmd.NewCmd("find", "/", "--name", "needle")
statusChan := findCmd.Start() // non-blocking
ticker := time.NewTicker(2 * time.Second)
// Print last line of stdout every 2s
go func() {
for range ticker.C {
status := findCmd.Status()
n := len(status.Stdout)
fmt.Println(status.Stdout[n-1])
}
}()
// Stop command after 1 hour
go func() {
<-time.After(1 * time.Hour)
findCmd.Stop()
}()
// Check if command is done
select {
case finalStatus := <-statusChan:
// done
default:
// no, still running
}
// Block waiting for command to exit, be stopped, or be killed
finalStatus := <-statusChan
}
c := exec.Command("cmd", "/C", "dir", "d:\\")
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr
c.Run()