I write simple script using Golang to grep a log file with some parameters. Here's my shell command
grep CRON var/log/sys | tail -5 | grep "cd /home/raka/repo && git status"
I want to run command above in Golang using os/exec package. Here's my code sniped.
var (
reader io.Reader
out []byte
err error
commandName string = "grep"
)
args := []string{"CRON", "/var/log/syslog", "| tail -6", "| grep \"git status\""}
cmd := exec.Command(commandName, args...)
r, err = cmd.StdoutPipe()
err = cmd.Start()
out, err = ioutil.ReadAll(r)
err = cmd.Wait()
return strings.Split(string(out), "\n")```
Currently, the sniped above doesn't work, because of exit status 2.
Any of you guys/ladies have solution for this problem? thank you so much.
Pipes (|) are implemented by a shell program (like bash). If you want to use them you should execute shell passing a command containing piped program invocations:
exec.Command("/bin/sh", "-c",
"grep CRON var/log/sys | tail -5 | grep \"cd /home/raka/repo && git status\"")
Related
I'm unable to echo a string into a new file , it works if I use a filename with some extension(.txt, .go, etc) but it doesn't work when I just use a filename without file extension
I want the below bash commands to be executed in golang
echo "testDir/*"> .git/info/sparse-checkout
git checkout <Branch Name>
code snippet:
// Remove the redirect from command
cmd := exec.Command("echo", "testDir/*")
// Make test file
testFile, err := os.Create(".git/info/sparse-checkout")
if err != nil {
panic(err)
}
defer testFile.Close()
// Redirect the output here (this is the key part)
cmd.Stdout = testFile
err = cmd.Start(); if err != nil {
panic(err)
}
cmd.Wait()
branchCmd := exec.Command("git checkout <Branch Name>")
The echo command just print the list of arguments. The command interpreter (shell) expand the * based on globing existing filenames.
Without the shell, you can’t echo anything. You may need to call the shell (bash for instance) to expand and echo it, or use the io package to list files based on this pattern
If I understand correctly : in your bash command, echo "testDir/*" doesn't expand anything, it just outputs the string "testDir/*". echo ... > file is just one of the gazillion ways to set the file content to a given value from your shell.
If you want to write a fixed string to a file in go, just write it :
_, err := testFile.Write([]byte("testDir/*\n"))
no need to start some external process to echo a value on stdout.
I have a bash script in which I am receiving response of an api in string format. The response is in the following format:
foo bar test stack over flow
Now I am having following bash script to convert it to array and process further:
#!/bin/bash
result=$(curl "API URL")
resp=($result)
for i in "${resp[#]}"
do
echo "$i"
done
Problem:
If I run this script manually in the terminal (by making it executable) it works fine. But when I try to run it by using Golang sh command
ExecuteCommand("sh /path/to/directory/test.sh")
func ExecuteCommand(command string) error{
cmd := exec.Command("sh", "-c",command)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
return err
}
fmt.Println("Result: " + out.String())
return nil
}
It gives me error:
test.sh: Syntax error: "(" unexpected
can someone help me what am I doing wrong ?
change these lines in your example
ExecuteCommand("/path/to/directory/test.sh")
func ExecuteCommand(command string) error{
cmd := exec.Command(command)
The kernel shebang will intercept the #! line and correctly run the script
U can also create a shortcut from "test.sh" file :
#!/bin/bash
$ sudo ln -s /path/to/directory/test.sh /usr/bin/testSH
If u arent root user give permission to the shortcut :
sudo chmod 777 /usr/bin/testSH
command := "testSH"
cmd := exec.Command("sh", "-c",command).Run()
I am trying to execute set of commands in Go using exec.Command(). Where I am trying to detach Gluster peer using Docker Exec.
fmt.Println("About to execute gluster peer detach")
SystemdockerCommand := exec.Command("sh", "-c", "docker exec ", "9aa1124", " gluster peer detach ", "192.168.1.1", " force")
var out bytes.Buffer
var stderr bytes.Buffer
SystemdockerCommand.Stdout = &out
SystemdockerCommand.Stderr = &stderr
err := SystemdockerCommand.Run()
if err != nil {
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
}
fmt.Println("System Docker exec : " + out.String())
I was expecting a result as "no peer to detatch". But got
exit status 1: "docker exec" requires at least 2 arguments.
Since you have used sh -c, the next parameter should be the full command or commands:
SystemdockerCommand := exec.Command("sh", "-c", "docker exec 9aa1124 gluster peer detach 192.168.1.1 force")
More generally, as in here:
cmd := exec.Command("/bin/sh", "-c", "command1 param1; command2 param2; command3; ...")
err := cmd.Run()
See this example:
sh := os.Getenv("SHELL") //fetch default shell
//execute the needed command with `-c` flag
cmd := exec.Command(sh, "-c ", `docker exec 9aa1124 ...`)
Or this one, putting your commands in a string first:
cmd := "cat /proc/cpuinfo | egrep '^model name' | uniq | awk '{print substr($0, index($0,$4))}'"
out, err := exec.Command("bash","-c",cmd).Output()
if err != nil {
return fmt.Sprintf("Failed to execute command: %s", cmd)
}
multipass exec kube-node-one -- bash -c "ls && ls -a"
Is there any way we can execute a multiple commands in exec.Command
No.
In my shell, I can exec command acme.sh --issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please and get output.
now I want to do that in go, and my code like:
cmd := exec.Command("bash", "-c", "acme.sh --issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please");
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("issue failed with error: %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
but I got error exit status 1.
and as the comment said, I separate argument:
exec.Command("bash", "-c", "acme.sh", "--issue", "--dns", "-d exmaple.com", "--yes-I-know-dns-manual-mode-enough-go-ahead-please");
but the result is that it exec acme.sh without parameters.
Using this script as acme.sh
#!/bin/bash
echo "$*"
with the program as given in the same directory the error you report occurs
However, if I add the current directory to the shell PATH
export PATH=.:$PATH
Then the program executes as hoped for, in the case of my version
$ go run c.go
combined out:
--issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please
Ok so that's the case when the bash -c takes a single string as the command (more about that in a moment)
If the command is issued like this
cmd := exec.Command("acme.sh", "--issue", "--dns", "-d exmaple.com", "--
yes-I-know-dns-manual-mode-enough-go-ahead-please")
Then as the later edits to your question state, the command acme.sh is run without arguments.
The problem is with the way that bash -c behaves.
From the man page
bash interprets the following options when it
is invoked:
-c If the -c option is present, then commands are read from the
first non-option argument command_string. If there are argu‐
ments after the command_string, they are assigned to the
positional parameters, starting with $0.
In your case what this means is that the first arg to bash -c is accepted as a command. The other args are lost as they are positional args to the new bash shell and not to the acme.sh command
See this for more details on bash -c https://unix.stackexchange.com/questions/144514/add-arguments-to-bash-c
Lastly, what I would do in this situation: Skip the "bash" "-c", ensure that the script has a correct bang line and rely on the kernel binfmt handler
exclude exit status 1 from err will get right result.
cmd := exec.Command("bash", "-c", "acme.sh --issue --dns -d exmaple.com --yes-I-know-dns-manual-mode-enough-go-ahead-please");
out, err := cmd.CombinedOutput()
if err != nil && err.Error() != "exit status 1" {
log.Fatalf("issue failed with error: %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
I am trying to invoke a git log from go and redirect the output to a given file.
cmdArgs = []string{"log", "--numstat", "--reverse", fmt.Sprintf("%s..HEAD", "89c98f5ec48c8ac383ea9e27d792c3dc77fa6240"), `--pretty="format:=%P %H %an %ae %ad %at %s %b"`}
cmdArgs = append(cmdArgs, ">> "+workingDir+"/logs/"+repoName+".log && cat "+workingDir+"/logs/"+repoName+".log")
cmd := exec.Command("git", cmdArgs...)
cmd.Dir = workingDir + repoName
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
fmt.Println("git", strings.Join(cmdArgs, " "), "in", workingDir+repoName)
fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
panic(err)
}
fails with
git log --numstat --reverse --pretty="format:=%P %H %an %ae %ad %at %s %b" 89c98f5ec48c8ac383ea9e27d792c3dc77fa6240..HEAD > /someplace/xx-tmp.log && cat /someplace/xx-tmp.log in /someplace
exit status 128: fatal: ambiguous argument ' > /someplace/xx-tmp.log && cat /someplace/xx-tmp.log: unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
Executing the command in bash directly poses no problem.
Go's cmd.Run() acts similarly to the C fork() and exec() process for starting a new program. This doesn't implicitly invoke a shell -- which is, from a security perspective, an extremely good thing; unnecessary shell invocation often leads to command injection vulnerabilities.
If you want capabilities that a shell can add -- here, redirection and compound command syntax -- but want to avoid the security risks, pass your data out-of-band from code:
cmdArgs = []string{
"-c", // tells interpreter that script is next argument
`outfile=$1; shift; "$#" >"$outfile" && cat "$outfile"`, // script to execute
"_", // this is $0
workingDir+"/logs/"+repoName+".log", // $1, becomes outfile
"git", "log", "--numstat", "--reverse", // remaining args are in "$#"
fmt.Sprintf("%s..HEAD", "89c98f5ec48c8ac383ea9e27d792c3dc77fa6240"),
"--pretty=format:=%P %H %an %ae %ad %at %s %b"
}
cmd := exec.Command("sh", cmdArgs...)
The above is equivalent to the following shell script:
#!/bin/sh
# ^^- not /bin/bash; this only guarantees support for POSIX syntax
outfile=$1 # assign first positional argument to variable "$outfile"
shift # rename $2 to $1, $3 to $2, etc
if "$#" >"$outfile"; then # run remaining arguments as a single command, stdout to outfile
cat "$outfile" # if that succeeded, then cat our "$outfile" to stdout
fi
Note that I removed the literal quotes from inside --pretty=. That's because when you run the command in a shell, those quotes are treated as syntax by the shell -- an instruction not to split on the spaces within the format string. Here, there is no shell parsing that string as code; if we left the quotes in, they would become a literal part of your format string.
The above being said, there's no good reason to use a shell for any of this. You can do the redirection in native Go trivially:
cmdArgs = []string{
"log", "--numstat", "--reverse",
fmt.Sprintf("%s..HEAD", "89c98f5ec48c8ac383ea9e27d792c3dc77fa6240"),
`--pretty=format:=%P %H %an %ae %ad %at %s %b`}
cmd := exec.Command("git", cmdArgs...)
cmd.Dir = workingDir + repoName
outputFile, err = os.OpenFile(workingDir+"/logs/"+repoName+".log",
os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644)
if err != nil { panic(err) }
defer outputFile.Close()
cmd.Stdout = outputFile
...and then run the cat as a separate exec.Command instance (if you have a good reason not to just implement it in native Go).