Repository navigation
Allow timeout for Exec to avoid hanging #225
Description
Activity
Great suggestions, @rcowham! There's been some previous discussion on this, if you haven't already seen it: in #207 and #105, for example. Would you like to review those and see if you can propose a concrete example program to demonstrate your idea?
Good feature proposals tend to satisfy as many as possible of the following criteria:
- Show that the lack of such a feature is a significant problem for a significant number of users
- Propose a feature that addresses the problem in a satisfactory way
- Include a runnable example program that shows how the proposed syntax or API would be used to solve the problem for a realistic use case
- Show that this is significantly better than any exisitng solution without the feature
For my use cases, a timeout on the whole pipeline is sufficient - just want to avoid an indefinite hang. Clearly this can be done by me putting it on a thread, and killing the process if required - but if library can do it...
err := script.Exec(cmd).WithTimeout(3*time.Second) if err == script.Timeout { // do something } else if err != nil { // do something else } s, err := script.Exec(cmd).WithTimeout(3*time.Second).Slice() // Similar error handling as above errbuf := new(bytes.Buffer) p := script.NewPipe().WithStderr(errbuf).WithTimeout(3*time.Second) result, err := p.Exec(cmd).<other methods> // Similar error handling as aboveA context would be the more idiomatic way to do this, wouldn't it?
A context would be fine - wasn't sure from reading other issues if that was the preferred way...
Just a reminder that this is with you (or whoever wants to work on it) to complete the four steps I outlined.
- added a commit that references this issue
on Oct 25, 2025 @bitfield I feel that a way to include timeout should be implemented
(1) Show that the lack of such a feature is a significant problem for a significant number of users
I am not sure of the userbase of this library, but here is an example of an issues
Writing a script to continuously monitor dns lookup on a server. DNS lookups can sometimes take longer then 200 msec, which is a bad thing and unwanted that should be logged.
There could be other cases, like running command that can get stuck in an infinite loop on invalid input, or invalid state of the system, or running other network monitoring commands that needs specific timeout
(2) Propose a feature that addresses the problem in a satisfactory way
Option 1 - Allow a new API
ExecWithContext(ctx context.Context, cmdLine string)that will stop the execution of the command whenctxis cancelled. We would need to useexec.CommandContextinstead ofexec.Command, and pass this ctx
Option 2 - Allow a new APIExecWithTimeout(cmdLine string, timeout time.Time)that will stop the execution of the command timeout is reached. We would need to useexec.CommandContextinstead ofexec.Command, and pass a new context with timeoutI prefer option 1
(3) Include a runnable example program that shows how the proposed syntax or API would be used to solve the problem for a realistic use case
Original code. (Note : There might be a better way for custom timeout impl)
Details
package main import ( "errors" "fmt" "strings" "time" "github.com/bitfield/script" ) func ExecuteWithTimeout(f func() string, timeout time.Duration) (string, error) { result := make(chan string, 1) go func() { result <- f() }() select { case msg := <-result: return msg, nil case <-time.After(timeout): return "", errors.New("timeout") } } func main() { for { script.File("servers.txt").FilterLine(func(url string) string { res, err := ExecuteWithTimeout(func() string { ip, _ := script.Exec(fmt.Sprintf("dig +short %s", url)).First(1).String() return strings.TrimSpace(ip) }, time.Millisecond*200) if err != nil { res = err.Error() } return fmt.Sprintf("[%v] %s -> %s", time.Now(), url, res) }).Stdout() time.Sleep(time.Second) } }
With ctx API, it could be easier
Options 1 more generic and needs boilerplate context management code
Details
package main import ( "context" "fmt" "strings" "time" "github.com/bitfield/script" ) func main() { for { script.File("servers.txt").FilterLine(func(url string) string { ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() ip, err := script.ExecWithContext(ctx, fmt.Sprintf("dig +short %s", url)).First(1).String() res := strings.TrimSpace(ip) if err != nil { res = err.Error() } return fmt.Sprintf("[%v] %s -> %s", time.Now(), url, res) }).Stdout() time.Sleep(time.Second) } }
Option 2 more specific, and easier to use
Details
package main import ( "fmt" "strings" "time" "github.com/bitfield/script" ) func main() { for { script.File("servers.txt").FilterLine(func(url string) string { ip, err := script.ExecWithTimeout(fmt.Sprintf("dig +short %s", url), 200*time.Millisecond). First(1).String() // ExecWithTimeout will create a fresh context and manage it res := strings.TrimSpace(ip) if err != nil { res = err.Error() } return fmt.Sprintf("[%v] %s -> %s", time.Now(), url, res) }).Stdout() time.Sleep(time.Second) } }
(4) Show that this is significantly better than any existing solution without the feature
This should be evident from the two examples.
I am not sure if I picked the best example for this, but there could be many other real use-cases that would benefit from this feature
@lazysegtree thanks for the very detailed and thoughtful contribution!
To be honest, while I completely agree that one can imagine cases where a timeout would be useful, there doesn't actually seem to be a large number of users currently wanting this feature. If there were, I think they'd be commenting on this issue.
It's probably not worth any of us putting a lot of time and effort into designing such a feature if there's no demand for it. Of course, if dozens of people proceed to comment here saying they'd like it, then by all means, let's start thinking!
A timeout feature would be super nice. I think already enough people want it
Reacted by John Arundel- [like] Robert Cowham reacted to your message:…________________________________ From: Rewinter ***@***.***> Sent: Saturday, January 17, 2026 3:15:19 PM To: bitfield/script ***@***.***> Cc: Robert Cowham ***@***.***>; Mention ***@***.***> Subject: Re: [bitfield/script] Allow timeout for Exec to avoid hanging (Issue #225) [https://avatars.githubusercontent.com/u/64508791?s=20&v=4]rew1nter left a comment (bitfield/script#225)<#225 (comment)> A timeout feature would be super nice. I think already enough people wants it — Reply to this email directly, view it on GitHub<#225 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/ACS5LBNJYCDHAPBPVXJYODT4HJGYPAVCNFSM6AAAAACQB43ATGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTONRTHE4TINJRGE>. You are receiving this because you were mentioned.Message ID: ***@***.***> This e-mail may contain information that is privileged or confidential. If you are not the intended recipient, please delete the e-mail and any attachments and notify us immediately. Perforce Software UK Ltd is registered in England and Wales as company no. 3816019 at the following address: The Capitol Building, Oldbury, Bracknell,Berkshire, RG12 8FZ, UK
Hey @bitfield , I am keen to get involved in contributing here, is this something I can pick up? I don't see a merged PR yet.
- added a commit that references this issue
on Jul 30, 2026 Fixed in #244
It would be nice to be able to specify a timeout to avoid commands hanging during execution.
Maybe something like the existing WithEnv(), but instead WithTimeout() or WithWaitDelay() to support the new (Go 1.20) WaitDelay?
https://pkg.go.dev/os/exec#Cmd.WaitDelay