46

I have a bunch of systems commands which are somwhat similar to appending new content to a file. I wrote a simple script to execute system commands, which works well if there are single words like 'ls' , 'date' etc. But if the command is greater than that, program dies.

The following is the code

package main

import (
    "fmt"
    "os/exec"
    "sync"
)

func exe_cmd(cmd string, wg *sync.WaitGroup) {
    fmt.Println(cmd)
    c = cmd.Str
    out, err := exec.Command(cmd).Output()
    if err != nil {
        fmt.Println("error occured")
        fmt.Printf("%s", err)
    }
    fmt.Printf("%s", out)
    wg.Done()
}

func main() {
    wg := new(sync.WaitGroup)
    wg.Add(3)

    x := []string{"echo newline >> foo.o", "echo newline >> f1.o", "echo newline >> f2.o"}
    go exe_cmd(x[0], wg)
    go exe_cmd(x[1], wg)
    go exe_cmd(x[2], wg)

    wg.Wait()
}

The following is the error i see

exec: "echo newline >> foo.o": executable file not found in $PATHexec: 
"echo newline >> f2.o": executable file not found in $PATHexec: 
"echo newline >> f1.o": executable file not found in $PATH 

I guess, this may be due to, not sending cmds and arguments seperately ( http://golang.org/pkg/os/exec/#Command ). I am wondering how to subvert this, since I don't know how many arguments will be there in my command which needs to be executed.

3 Answers 3

107

I found a relatively decent way to achieve the same .

out, err := exec.Command("sh","-c",cmd).Output()

Works for me until now. Still finding better ways to achieve the same.

Edit1:

Finally a easier and efficient (atleast so far) way to do would be like this

func exeCmd(cmd string, wg *sync.WaitGroup) {
  fmt.Println("command is ",cmd)
  // splitting head => g++ parts => rest of the command
  parts := strings.Fields(cmd)
  head := parts[0]
  parts = parts[1:len(parts)]

  out, err := exec.Command(head,parts...).Output()
  if err != nil {
    fmt.Printf("%s", err)
  }
  fmt.Printf("%s", out)
  wg.Done() // Need to signal to waitgroup that this goroutine is done
}

Thanks to variadic arguments in go and people that pointed that out to me :)

Sign up to request clarification or add additional context in comments.

4 Comments

it works fine for the string mentioned in problem. But if you try to execute something like "echo new >> test.txt" it breaks. :(
If you want to do commands with multiple parts like "command1 && command2" then the first (pre-ediut) method works, even though the second fails.
I think it will fail on cases like date +"%y %m %d"
For Windows the equivalent is cmd := exec.Command("C:\\Windows\\System32\\cmd.exe", "/c", command). You should probably use exec.LookPath to get the system's shell path from $PATH/%PATH%.
11

For exec.Command() the first argument needs to be the path to the executable. Then the remaining arguments will be supplied as arguments to the executable. Use strings.Fields() to help split the word into a []string.

Example:

package main

import (
    "fmt"
    "os/exec"
    "sync"
    "strings"
)

func exe_cmd(cmd string, wg *sync.WaitGroup) {
    fmt.Println(cmd)
            parts := strings.Fields(cmd)
    out, err := exec.Command(parts[0],parts[1]).Output()
    if err != nil {
        fmt.Println("error occured")
        fmt.Printf("%s", err)
    }
    fmt.Printf("%s", out)
    wg.Done()
}

func main() {
    wg := new(sync.WaitGroup)
    commands := []string{"echo newline >> foo.o", "echo newline >> f1.o", "echo newline >> f2.o"}
    for _, str := range commands {
        wg.Add(1)
        go exe_cmd(str, wg)
    }
    wg.Wait()
}

Here's an alternative approach that just writes all the commands to a file then executes that file within the context of the new created output directory.

Example 2

package main

import (
    "os"
    "os/exec"
    "fmt"
    "strings"
    "path/filepath"
)
var (
    output_path = filepath.Join("./output")
    bash_script = filepath.Join( "_script.sh" )
)
func checkError( e error){
    if e != nil {
        panic(e)
    }
}
func exe_cmd(cmds []string) {
    os.RemoveAll(output_path)
    err := os.MkdirAll( output_path, os.ModePerm|os.ModeDir )
    checkError(err)
    file, err := os.Create( filepath.Join(output_path, bash_script))
    checkError(err)
    defer file.Close()
    file.WriteString("#!/bin/sh\n")
    file.WriteString( strings.Join(cmds, "\n"))
    err = os.Chdir(output_path)
    checkError(err)
    out, err := exec.Command("sh", bash_script).Output()
    checkError(err)
    fmt.Println(string(out))
}

func main() {
    commands := []string{
    "echo newline >> foo.o",
    "echo newline >> f1.o",
    "echo newline >> f2.o",
    }
   exe_cmd(commands)
}

5 Comments

its executing it. but not in real sense. the file should have the newline appeneded to it. But it aint happening.
I think it would be better if you wrote the commands to a bash script, then used go to execute the script. Otherwise you're going to have to add support for standard input and output.
hmm. thats nice. But it will be nice to have if I could somehow truncate the string and send the []string elements are a parameter to Command() . That will obviate the need of doing all these.
Rahul, I believe what you are referring to is a Variadic value. e.g: exec.Command("ls", args...).Output() en.wikipedia.org/wiki/Variadic_function
small correction: the second argument of Command should be parts[1:]..., otherwise it wont work.
8
    out, _ := exec.Command("sh", "-c", "date +\"%Y-%m-%d %H:%M:%S %Z\"").Output()
    exec.Command("sh","-c","ls -al -t | grep go >>test.txt").Output()
    fmt.Printf("%s\n\n",out)

Tested couple cases and all work good. This is a lifesaver if you are dealing with quick shell commands in your program. Not tested with complex cases.

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.