Skip to content
Merged
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
language: go
sudo: false
go:
- 1.7
- 1.8

- 1.9
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cover

script:
- $HOME/gopath/bin/goveralls -service=travis-ci
- $GOPATH/bin/goveralls -service=travis-ci -race -package github.com/go-cmd/cmd -show
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

[![Go Report Card](https://goreportcard.com/badge/github.com/go-cmd/cmd)](https://goreportcard.com/report/github.com/go-cmd/cmd) [![Build Status](https://travis-ci.org/go-cmd/cmd.svg?branch=master)](https://travis-ci.org/go-cmd/cmd) [![Coverage Status](https://coveralls.io/repos/github/go-cmd/cmd/badge.svg?branch=master)](https://coveralls.io/github/go-cmd/cmd?branch=master) [![GoDoc](https://godoc.org/github.com/go-cmd/cmd?status.svg)](https://godoc.org/github.com/go-cmd/cmd)

This package is a small but very useful wrapper around [os/exec.Cmd](https://golang.org/pkg/os/exec/#Cmd) that makes it dead simple and safe to run commands in asynchronous, highly concurrent, real-time applications. Here's the look and feel:
This package is a small but very useful wrapper around [os/exec.Cmd](https://golang.org/pkg/os/exec/#Cmd) that makes it safe and simple to run external commands in highly concurrent, asynchronous, real-time applications. Here's the look and feel:

```go
import "github.com/go-cmd/cmd"

// Start a long-running process, capture stdout and stderr
c := cmd.NewCmd("find", "/", "--name" "needle")
statusChan := c.Start()
findCmd := cmd.NewCmd("find", "/", "--name" "needle")
statusChan := findCmd.Start() // non-blocking

ticker := time.NewTicker(2 * time.Second)

Expand All @@ -25,13 +25,13 @@ go func() {
// Stop command after 1 hour
go func() {
<-time.After(1 * time.Hour)
c.Stop()
findCmd.Stop()
}()

// Check if command is done
select {
case finalStatus := <-statusChan:
// yes!
// done
default:
// no, still running
}
Expand All @@ -40,7 +40,7 @@ default:
finalStatus := <-statusChan
```

That's it, only three methods: `Start`, `Stop`, and `Status`. Although free, here are the selling points of `go-cmd/Cmd`:
That's it, only three methods: `Start`, `Stop`, and `Status`. When possible, it's better to use `go-cmd/Cmd` than `os/exec.Cmd` because `go-cmd/Cmd` provides:

1. Channel-based fire and forget
1. Real-time stdout and stderr
Expand All @@ -58,13 +58,13 @@ As the example above shows, starting a command immediately returns a channel to
c := cmd.NewCmd("foo")
s := <-c.Start()
```
To achieve similar with Go built-in `Cmd` requires everything this package already does.
To achieve similar with `os/exec.Cmd` requires everything this package already does.

### Real-time stdout and stderr

It's common to want to read stdout or stderr _while_ the command is running. The common approach is to call [StdoutPipe](https://golang.org/pkg/os/exec/#Cmd.StdoutPipe) and read from the provided `io.ReadCloser`. This works but it causes a race condition (that `go test -race` detects) and the docs say not to do it: "it is incorrect to call Wait before all reads from the pipe have completed".
It's common to want to read stdout or stderr _while_ the command is running. The common approach is to call [StdoutPipe](https://golang.org/pkg/os/exec/#Cmd.StdoutPipe) and read from the provided `io.ReadCloser`. This works but it's wrong because it causes a race condition (that `go test -race` detects) and the docs say it's wrong: "it is incorrect to call Wait before all reads from the pipe have completed. [...] it is incorrect to call Run when using StdoutPipe".

The proper solution is to set the `io.Writer` of `Stdout`. To be thread-safe and non-racey, this requires further work to write while possibly N-many goroutines read. `go-cmd/Cmd` has already done this work.
The proper solution is to set the `io.Writer` of `Stdout`. To be thread-safe and non-racey, this requires further work to write while possibly N-many goroutines read. `go-cmd/Cmd` has done this work.

### Real-time status

Expand All @@ -88,11 +88,11 @@ Speaking of that struct above, Go built-in `Cmd` does not put all the return inf

### Proper process termination

It's been said that everyone love's a good mystery. Then here's one: _process group ID_. If you know, then wow, congratulations! If not, don't feel bad. It took me hours one Saturday evening to solve this mystery. Let's just say that Go built-in [Wait](https://golang.org/pkg/os/exec/#Cmd.Wait) can still block even after the command is killed. But not `go-cmd/Cmd`. You can rely on `Stop` in this package.
[os/exec/Cmd.Wait](https://golang.org/pkg/os/exec/#Cmd.Wait) can block even after the command is killed. That can be surprising and cause problems. But `go-cmd/Cmd.Stop` reliably terminates the command, no surprises. The issue has to do with process group IDs. It's common to kill the command PID, but usually one needs to kill its process group ID instead. `go-cmd/Cmd.Stop` implements the necessary low-level magic to make this happen.

### 100% test coverage, no race conditions

Enough said.
In addition to 100% test coverage and no race conditions, this package is actively used in production environments.

---

Expand Down
Loading