Isn't this Golang program supposed to output a directory listing to stdout?
It compiles ok, but does nothing.
package main
import "exec"
func main() {
argv := []string{"-la"}
envv := []string{}
exec.Run("ls", argv, envv, "", exec.DevNull, exec.PassThrough, exec.MergeWithStdout)
}
this works:
package main
import "exec"
func main() {
cmd, err := exec.Run("/bin/ls", []string{"/bin/ls", "-la"}, []string{}, "", exec.DevNull, exec.PassThrough, exec.PassThrough)
if (err != nil) {
return
}
cmd.Close()
}
You could also do it in native go using: ioutil.ReadDir(dir), like so:
//listdir.go
package main
import (
"os"
"io/ioutil"
"fmt"
)
func ListDir(dir string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dir)
}
func main() {
dir := "./"
if len(os.Args) > 1 {
dir = os.Args[1]
}
fi, err := ListDir(dir)
if err != nil {
fmt.Println("Error", err)
}
for _, f := range fi {
d := "-"
if f.IsDir() { d = "d" }
fmt.Printf("%s %o %d %s %s\n", d, f.Mode() & 0777, f.Size(), f.ModTime().Format("Jan 2 15:04"), f.Name())
}
}
Checkout the documentation available for ioutil and os packages.
By default exec.Command will leave standard input, output and error connected to /dev/null. So, your 'ls' command is running fine but the output is just being thrown away. If you add:
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
before the exec.Run call then your output will go where you probably expect it.
exec.Run replaces your program with the one it executes -- it never returns to your app. This means that when 'cd' completes, it will exit as normal, and the only effect should be of changing the directory; 'ls' will never run.
Related
With go1.13.6 on macOs Mojave 10.14.6, the flockCmdMain function from the following program does not return in the case where the timeout has reached. I've tested with the same go version on Linux and FreeBSD, and do not have this problem.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"time"
"golang.org/x/sys/unix"
)
type FlockOptions struct {
File string // file or dir to place the lock on
Command string // shell command to run
TimeoutSeconds int
}
func main() {
var opt = FlockOptions{}
flag.StringVar(&opt.File, "f", "", "[REQUIRED] File or directory to lock, must already exist")
flag.StringVar(&opt.Command, "c", "", "Pass a single command to the shell with -c")
flag.IntVar(&opt.TimeoutSeconds, "w", 0, "Time out if the lock cannot be acquired within the specified number of seconds.(negative or 0, the default, means no timeout)")
flag.Parse()
if opt.File == "" {
fmt.Fprintf(os.Stderr, "Supply a file or directory to lock with -f <file/dir>\n\n")
flag.Usage()
os.Exit(1)
}
exitCode, err := flockCmdMain(opt)
if err != nil {
log.Fatal(err)
}
os.Exit(exitCode)
}
func flockCmdMain(opts FlockOptions) (int, error) {
f, err := os.Open(opts.File)
if err != nil {
return -1, err
}
defer f.Close()
fd := int(f.Fd())
select {
case <-timeoutAfter(time.Duration(opts.TimeoutSeconds) * time.Second):
fmt.Println("DEBUG: the timeout channel has fired")
return -1, fmt.Errorf("timed out")
case err = <-lock(fd):
if err != nil {
return -1, err
}
defer unix.Flock(fd, unix.LOCK_UN)
cmd := exec.Command("bash", []string{"-c", opts.Command}...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run() // err discarded
return cmd.ProcessState.ExitCode(), nil
}
}
func lock(fd int) <-chan error {
flockOpts := unix.LOCK_EX
lockErr := make(chan error)
go func() {
lockErr <- unix.Flock(fd, flockOpts)
}()
return lockErr
}
// time out after d, never times out if d is zero or negative
func timeoutAfter(d time.Duration) <-chan time.Time {
switch {
case d <= 0:
return make(chan time.Time)
default:
return time.NewTimer(d).C
}
}
To trigger the problem, compile the above code to an executable called flock, then run the following in one terminal, which holds an exclusive flock on the /tmp directory for 123 seconds.
./flock -f /tmp -c 'sleep 123'
In another terminal, run the following, which also attempts to place an exclusive flock on /tmp, and you will see that the timeout channel firing, and then the flockCmdMain function hangs without returning back to main.
./flock -f /tmp -w 1 -c 'echo locked'
Why does the flockCmdMain function not return on macOs?
This question already has answers here:
Exec a shell command in Go
(9 answers)
Closed 3 years ago.
So I would like to create a command shell in Go. My goal is to use its capabilities in portable system tools I'm thinking about writing. I think a great starting point would be something that can demonstrate the basics of running system commands and allowing the user to interact with the system shell.
I have such a solution! I actually have it posted in a Gist at gist.github.com/lee8oi/a8a90f559fe48355f800
Its a nice little demonstration that uses Go's os/exec package to directly pipe input/output/error to the command instance. When "bash" runs you'll be sitting at a very simple prompt. You can interact with it much like you would with bash (but still pretty simplified). When you run a regular command the output will be displayed then the command will exit (like with "ping"). I think this would make a great starting point for tinkering with ways to interact with the system shell in Go.
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"os/exec"
"sync"
)
func getPipes(c *exec.Cmd) (inp io.Writer, outp, errp io.Reader) {
var err error
if inp, err = c.StdinPipe(); err != nil {
log.Fatal(err)
}
if outp, err = c.StdoutPipe(); err != nil {
log.Fatal(err)
}
if errp, err = c.StderrPipe(); err != nil {
log.Fatal(err)
}
return
}
func pipe(wg *sync.WaitGroup, inp io.Reader, outp io.Writer) {
if wg != nil {
defer wg.Done()
}
r := bufio.NewReader(inp)
for {
c, err := r.ReadByte()
if err != nil {
return
}
fmt.Fprintf(outp, "%s", string(c))
}
}
func Command(args ...string) {
var cmd *exec.Cmd
var wg sync.WaitGroup
if len(args) > 1 {
cmd = exec.Command(args[0], args[1:]...)
} else {
cmd = exec.Command(args[0])
}
inp, outp, errp := getPipes(cmd)
wg.Add(1)
go pipe(&wg, errp, os.Stderr)
wg.Add(1)
go pipe(&wg, outp, os.Stdout)
go pipe(nil, os.Stdin, inp)
if err := cmd.Start(); err != nil {
log.Fatal("start ", err)
}
wg.Wait()
}
func main() {
Command("bash")
Command("date")
Command("echo", "some text")
Command("ping", "-c 3", "www.google.com")
}
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()
I'm writing a small program with an interpreter, I would like to pipe any command that is not recognized by my shell to bash, and print the output as if written in a normal terminal.
func RunExtern(c *shell.Cmd) (string, os.Error) {
cmd := exec.Command(c.Cmd(), c.Args()...)
out, err := cmd.Output()
return string(out), err
}
this is what I've written so far, but it only executes a program with its args, I would like to send the whole line to bash and get the output, any idea how to do so ?
For example, to list directory entries in columns,
package main
import (
"exec"
"fmt"
"os"
)
func BashExec(argv []string) (string, os.Error) {
cmdarg := ""
for _, arg := range argv {
cmdarg += `"` + arg + `" `
}
cmd := exec.Command("bash", "-c", cmdarg)
out, err := cmd.Output()
return string(out), err
}
func main() {
out, err := BashExec([]string{`ls`, `-C`})
if err != nil {
fmt.Println(err)
}
fmt.Println(out)
}
Let's say I want to run 'ls' in a go program, and store the results in a string. There seems to be a few commands to fork processes in the exec and os packages, but they require file arguments for stdout, etc. Is there a way to get the output as a string?
There is an easier way now:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}
Where out is the standard output. It's in the format []byte, but you can change it to string easily with:
string(out)
You can also use CombinedOutput() instead of Output() which returns standard output and standard error.
exec.Command
To get both stdout and stderr into separate strings, you can use byte buffers like so:
cmd := exec.Command("date")
var outb, errb bytes.Buffer
cmd.Stdout = &outb
cmd.Stderr = &errb
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Println("out:", outb.String(), "err:", errb.String())
cmd := exec.Command("ls", "-al")
output, _ := cmd.CombinedOutput()
fmt.Println(string(output))
or
cmd := exec.Command(name, arg...)
stdout, err := cmd.StdoutPipe()
cmd.Stderr = cmd.Stdout
if err != nil {
return err
}
if err = cmd.Start(); err != nil {
return err
}
for {
tmp := make([]byte, 1024)
_, err := stdout.Read(tmp)
fmt.Print(string(tmp))
if err != nil {
break
}
}
I used this with a recent version of GO (~1.11)
// CmdExec Execute a command
func CmdExec(args ...string) (string, error) {
baseCmd := args[0]
cmdArgs := args[1:]
log.Debugf("Exec: %v", args)
cmd := exec.Command(baseCmd, cmdArgs...)
out, err := cmd.Output()
if err != nil {
return "", err
}
return string(out), nil
}
// Usage:
// out, err := CmdExec("ls", "/home")
Two options, depending on the paradigm you prefer:
os.ForkExec()
exec.Run()
Use exec.Run, passing Pipe for stdout. Read from the pipe that it returns.
If you are wanting string output, strings.Builder is more efficient [1] than
bytes.Buffer:
package main
import (
"os/exec"
"strings"
)
func main() {
c, b := exec.Command("go", "version"), new(strings.Builder)
c.Stdout = b
c.Run()
print(b.String())
}
https://golang.org/pkg/bytes#Buffer.String
Edit: This answer is obsolete. Please see Fatih Arslan's answer below.
Use exec.Run by specifying Pipe as the stdout (and stderr if you want). It will return cmd, which contains an os.File in the Stdout (and Stderr) fields. Then you can read it using for example ioutil.ReadAll.
Example:
package main
import (
"exec";
"io/ioutil";
)
func main() {
if cmd, e := exec.Run("/bin/ls", nil, nil, exec.DevNull, exec.Pipe, exec.MergeWithStdout); e == nil {
b, _ := ioutil.ReadAll(cmd.Stdout)
println("output: " + string(b))
}
}