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.

peterh
  • 11,875
  • 18
  • 85
  • 108
Rahul
  • 11,129
  • 17
  • 63
  • 76

3 Answers3

106

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

0xInfection
  • 2,676
  • 1
  • 19
  • 34
Rahul
  • 11,129
  • 17
  • 63
  • 76
  • 5
    it works fine for the string mentioned in problem. But if you try to execute something like "echo new >> test.txt" it breaks. :( – Rahul Dec 07 '13 at 06:41
  • 4
    If you want to do commands with multiple parts like "command1 && command2" then the first (pre-ediut) method works, even though the second fails. – Frederick Jun 04 '14 at 04:13
  • 3
    I think it will fail on cases like `date +"%y %m %d"` – csyangchen Mar 14 '16 at 09:05
  • 2
    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%`. – Lomanic Jun 26 '17 at 10:25
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)
}
Larry Battle
  • 9,008
  • 4
  • 41
  • 55
  • its executing it. but not in real sense. the file should have the newline appeneded to it. But it aint happening. – Rahul Dec 07 '13 at 04:23
  • 1
    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. – Larry Battle Dec 07 '13 at 04:29
  • 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 Dec 07 '13 at 05:43
  • Rahul, I believe what you are referring to is a Variadic value. e.g: exec.Command("ls", args...).Output() http://en.wikipedia.org/wiki/Variadic_function – WookooUK Mar 26 '14 at 16:01
  • small correction: the second argument of `Command` should be `parts[1:]...`, otherwise it wont work. – Matteo Sep 23 '21 at 01:44
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.

rjni
  • 471
  • 7
  • 7