{"id":13612835,"url":"https://github.com/go-cmd/cmd","last_synced_at":"2025-04-13T12:32:45.429Z","repository":{"id":18319974,"uuid":"84010400","full_name":"go-cmd/cmd","owner":"go-cmd","description":"Non-blocking external commands in Go with streaming output","archived":false,"fork":false,"pushed_at":"2024-06-25T01:36:31.000Z","size":115,"stargazers_count":940,"open_issues_count":10,"forks_count":128,"subscribers_count":21,"default_branch":"master","last_synced_at":"2025-01-08T19:52:15.068Z","etag":null,"topics":["asynchronous","command","golang","non-blocking","real-time"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/go-cmd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-05T23:30:39.000Z","updated_at":"2025-01-03T10:32:46.000Z","dependencies_parsed_at":"2022-08-07T09:00:39.285Z","dependency_job_id":"34e2696c-d81d-42e7-a6fc-4b9ff90b0865","html_url":"https://github.com/go-cmd/cmd","commit_stats":{"total_commits":92,"total_committers":17,"mean_commits":5.411764705882353,"dds":0.3152173913043478,"last_synced_commit":"500562c204744af1802ae24316a7e0bf88dcc545"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-cmd%2Fcmd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-cmd%2Fcmd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-cmd%2Fcmd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go-cmd%2Fcmd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/go-cmd","download_url":"https://codeload.github.com/go-cmd/cmd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248714726,"owners_count":21149954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","command","golang","non-blocking","real-time"],"created_at":"2024-08-01T20:00:35.135Z","updated_at":"2025-04-13T12:32:45.135Z","avatar_url":"https://github.com/go-cmd.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# go-cmd/Cmd\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/go-cmd/cmd)](https://goreportcard.com/report/github.com/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)\n[![Go Reference](https://pkg.go.dev/badge/github.com/go-cmd/cmd/.svg)](https://pkg.go.dev/github.com/go-cmd/cmd/)\n\nThis package is a small but very useful wrapper around [os/exec.Cmd](https://pkg.go.dev/os/exec#Cmd) that makes it safe and simple to run external commands in highly concurrent, asynchronous, real-time applications. It works on Linux, macOS, and Windows. Here's the basic usage:\n\n```go\nimport (\n\t\"fmt\"\n\t\"time\"\n\t\"github.com/go-cmd/cmd\"\n)\n\nfunc main() {\n\t// Start a long-running process, capture stdout and stderr\n\tfindCmd := cmd.NewCmd(\"find\", \"/\", \"--name\", \"needle\")\n\tstatusChan := findCmd.Start() // non-blocking\n\n\tticker := time.NewTicker(2 * time.Second)\n\n\t// Print last line of stdout every 2s\n\tgo func() {\n\t\tfor range ticker.C {\n\t\t\tstatus := findCmd.Status()\n\t\t\tn := len(status.Stdout)\n\t\t\tfmt.Println(status.Stdout[n-1])\n\t\t}\n\t}()\n\n\t// Stop command after 1 hour\n\tgo func() {\n\t\t\u003c-time.After(1 * time.Hour)\n\t\tfindCmd.Stop()\n\t}()\n\n\t// Check if command is done\n\tselect {\n\tcase finalStatus := \u003c-statusChan:\n\t\t// done\n\tdefault:\n\t\t// no, still running\n\t}\n\n\t// Block waiting for command to exit, be stopped, or be killed\n\tfinalStatus := \u003c-statusChan\n}\n```\n\nThat'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:\n\n1. Channel-based fire and forget\n1. Real-time stdout and stderr\n1. Real-time status\n1. Complete and consolidated return\n1. Proper process termination\n1. _100%_ test coverage, no race conditions\n\n### Channel-based fire and forget\n\nAs the example above shows, starting a command immediately returns a channel to which the final status is sent when the command exits for any reason. So by default commands run asynchronously, but running synchronously is possible and easy, too:\n\n```go\n// Run foo and block waiting for it to exit\nc := cmd.NewCmd(\"foo\")\ns := \u003c-c.Start()\n```\nTo achieve similar with `os/exec.Cmd` requires everything this package already does.\n\n### Real-time stdout and stderr\n\nIt's common to want to read stdout or stderr _while_ the command is running. The common approach is to call [StdoutPipe](https://pkg.go.dev/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:\n\n\u003e It is thus incorrect to call Wait before all reads from the pipe have completed. For the same reason, it is incorrect to call Run when using StdoutPipe.\n\nThe 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.\n\n### Real-time status\n\nSimilar to real-time stdout and stderr, it's nice to see, for example, elapsed runtime. This package allows that: `Status` can be called any time by any goroutine, and it returns this struct:\n```go\ntype Status struct {\n    Cmd      string\n    PID      int\n    Complete bool\n    Exit     int\n    Error    error\n    Runtime  float64 // seconds\n    Stdout   []string\n    Stderr   []string\n}\n```\n\n### Complete and consolidated return\n\nSpeaking of that struct above, Go built-in `Cmd` does not put all the return information in one place, which is fine because Go is awesome! But to save some time, `go-cmd/Cmd` uses the `Status` struct above to convey all information about the command. Even when the command finishes, calling `Status` returns the final status, the same final status sent to the status channel returned by the call to `Start`.\n\n### Proper process termination\n\n[os/exec/Cmd.Wait](https://pkg.go.dev/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.\n\n### 100% test coverage, no race conditions\n\nIn addition to 100% test coverage and no race conditions, this package is actively used in production environments.\n\n---\n\n## Acknowledgements\n\n[Brian Ip](https://github.com/BrianIp) wrote the original code to get the exit status. Strangely, Go doesn't just provide this, it requires magic like `exiterr.Sys().(syscall.WaitStatus)` and more.\n\n---\n\n## License\n\n[MIT](LICENSE) © go-Cmd.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-cmd%2Fcmd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgo-cmd%2Fcmd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo-cmd%2Fcmd/lists"}