Skip to content

Instantly share code, notes, and snippets.

@lee8oi
Last active September 10, 2024 18:10
Show Gist options
  • Save lee8oi/ec404fa99ea0f6efd9d1 to your computer and use it in GitHub Desktop.
Save lee8oi/ec404fa99ea0f6efd9d1 to your computer and use it in GitHub Desktop.
Using os.StartProcess() in Go for platform-independent system command execution.
package main
import (
"os"
"os/exec"
)
func Start(args ...string) (p *os.Process, err error) {
if args[0], err = exec.LookPath(args[0]); err == nil {
var procAttr os.ProcAttr
procAttr.Files = []*os.File{os.Stdin,
os.Stdout, os.Stderr}
p, err := os.StartProcess(args[0], args, &procAttr)
if err == nil {
return p, nil
}
}
return nil, err
}
func main() {
if proc, err := Start("ping", "-c 3", "www.google.com"); err == nil {
proc.Wait()
}
if proc, err := Start("zsh"); err == nil {
proc.Wait()
}
}
@lee8oi
Copy link
Author

lee8oi commented Mar 19, 2015

I really like how this runs commands so seamlessly. When you run bash, its pretty much the same as running bash in a terminal. Though this example is platform-independent, thanks to the os package, so bash might fail in Windows. But whatever shell or command prompt you have should work.

@lee8oi
Copy link
Author

lee8oi commented Mar 19, 2015

The main bug I noticed with the syscall.ForkExec version is the backspacing bug. Whatever you type stays on the screen even if you backspace over those keys to retype them. But the os.StartProcess version does not appear to have this issue.
https://gist.github.com/lee8oi/ec404fa99ea0f6efd9d1/10c8de1d48bc896a99d913d8f46e7057d30da717

@jeffj1
Copy link

jeffj1 commented Oct 1, 2019

[Update] This bug will not occur, because line 18 will always return an err != nil, which is picked up by your main function.

Hello lee,

Thanks for this code, a good way to wrap around the low level os.Process* call.

There is a potential bug with the code however, in the event that exec.LookPath fails, the start function will return *os.Process = nil (line 18) , this will cause problems in your main function as a nil pointer dereference issue at proc.Wait, this should be removed.

Regards,
Jeff J

@coolaj86
Copy link

coolaj86 commented Oct 8, 2019

I just came across some code for this today... but I don't understand why would you use os.StartProcess instead of exec.Command. What's the advantage?

@lee8oi
Copy link
Author

lee8oi commented Oct 8, 2019 via email

@jeffj1
Copy link

jeffj1 commented Oct 9, 2019

I just came across some code for this today... but I don't understand why would you use os.StartProcess instead of exec.Command. What's the advantage?

* https://golang.org/pkg/os/exec/

os/Exec is a higher level implementation of the os.Process, with os.StartProcess, you can get the ProcessState with which you can get more control over the process than os/exec. For example, I had to run a commandline process which locks and when it crashes golang hangs until the lock is removed, but with processState.Release option I was able to release the process resources thereby allowing my code to run to completion. I could have used the os/exec.Start and os/exec.Wait() but it did not work for me.

@rsvix
Copy link

rsvix commented Sep 10, 2024

Hi, thank you for sharing.
Do you know how i can capture the output stream from the StartProcess command in realtime?

@lee8oi
Copy link
Author

lee8oi commented Sep 10, 2024 via email

@rsvix
Copy link

rsvix commented Sep 10, 2024

Thank you @lee8oi
I've tried using io pipes, MultiReader and bufio.NewScanner, but with no luck

For anyone reading, im trying to do something like this, but using os.StartProcess instead of exec.Command

multi := io.MultiReader(stdout, stderr)

if err := cmd.Start(); err != nil {
return err
}

in := bufio.NewScanner(multi)

for in.Scan() {
log.Printf(in.Text()) // write each line to the log or anything else
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment