A drop-in replacement for sleep that can show you how much of the wait is left.
sleepgo -p 30 # wait 30 seconds, with a progress bar
sleepgo -c 5m # wait 5 minutes, with a countdown
sleepgo 1m 30s # wait 90 seconds silently, exactly like sleepSingle binary, no runtime dependencies, nothing outside the Go standard library.
sleep has two flags: --help and --version. There is no way to ask it for a countdown, a progress bar, or any sign of life at all. Everything people call a flag on sleep is really part of the duration operand (sleep 1m 30s).
So the wait for a long backup, a rate limit window, or a deploy cooldown happens behind a blank line, and you end up writing this again:
for i in $(seq 30 -1 1); do
printf '\rwaiting: %2ds ' "$i"
sleep 1
done
echosleepgo is that, as a flag. Called without flags it behaves like sleep, so you can put it in the same places.
With Go 1.21 or newer:
go install github.com/didvc/sleepgo@latestThe binary lands in $(go env GOPATH)/bin, usually ~/go/bin. Add that to your PATH if it is not there already.
From source:
git clone https://github.com/didvc/sleepgo
cd sleepgo
go build -o sleepgo .Or grab a prebuilt binary from the releases page.
sleepgo 5 # five seconds, silent
sleepgo -p 5 # five seconds, progress bar
sleepgo -c 5 # five seconds, countdown only
sleepgo -v 5 # print the wake-up time, then wait
sleepgo -p 2h # two hours
sleepgo -p 1h 30m # ninety minutes; operands add up
sleepgo -p inf # wait until interrupted| Flag | What it does |
|---|---|
-p, --progress |
Draw a progress bar. |
-c, --countdown |
Show the remaining time only. |
-v, --verbose |
Print the wake-up time before waiting and the elapsed time after. |
-q, --quiet |
Silence everything, including the flags above. |
-w N, --width N |
Assume the terminal is N columns wide. |
--style NAME |
Bar glyphs: unicode (default) or ascii. |
--no-color |
Turn off colour. |
-h, --help |
Print usage. |
-V, --version |
Print the version. |
Options can go before, after, or between the duration operands. Short options bundle, so -pv and -pw40 both work, and -- ends option parsing.
sleepgo -p 30 # same thing
sleepgo 30 -p # ...
sleepgo 1m -p 30s # ...The operand format is the one sleep uses.
| You write | You get |
|---|---|
30 |
30 seconds |
1.5 |
1.5 seconds |
90s |
90 seconds |
5m |
5 minutes |
2h |
2 hours |
1d |
1 day |
1h 30m |
90 minutes |
1m 30s 500ms ❌ |
rejected — ms is not a suffix |
inf, infinity |
wait until interrupted |
Suffixes are s (seconds, the default), m, h and d. Several operands in a row are added together. Fractions and exponents are fine: 0.25, .5 and 1234e-3 all mean the same wait as 1.234.
The bar fills as the wait goes on, with the total on the left and the time remaining on the right.
-c drops the bar and keeps the number, which suits a long wait where you only glance at it occasionally.
Terminals and fonts that mangle block glyphs get --style=ascii.
In a small split or a tmux pane, the display drops the parts that no longer fit, so it always stays on one line.
-v tells you when the wait will be over before it starts, which is handy for anything longer than a coffee break.
Wait out a rate limit and see the time go down:
sleepgo -c 15m && curl -s https://api.example.com/v1/thingsPut a visible pause between steps in a script, but only when someone is watching:
[ -t 2 ] && sleepgo -p 10 || sleepgo 10Retry with a visible backoff:
for attempt in 1 2 3 4 5; do
fetch_the_thing && break
echo "attempt $attempt failed, backing off"
sleepgo -c "$(( attempt * 10 ))"
doneHold a container or a shell open until something kills it:
sleepgo infGive yourself a moment to cancel before something destructive runs:
echo "wiping $TARGET — Ctrl-C to stop"
sleepgo -p 10
rm -rf "$TARGET"With no flags, sleepgo and sleep do the same thing, so an alias is enough:
alias sleep=sleepgoTo get the progress bar without typing -p every time, set an environment variable:
export SLEEPGO_PROGRESS=1Scripts that want the old silent behaviour can still ask for it with -q, and SLEEPGO_PROGRESS=0 turns the default back off.
Since the display is written to stderr, redirecting or piping stdout leaves it alone:
sleepgo -p 5 > results.txt # bar still visible
sleepgo -p 5 | while read -r l; do :; doneCtrl-C stops the wait, clears the bar and puts your cursor back. The process ends the same way sleep does when interrupted, so a Ctrl-C inside a loop still breaks out of the loop.
Ctrl-Z suspends it. The display gets out of the way first, and fg brings it back and carries on drawing. Time keeps passing while a process is stopped, so a long suspension can mean the wait is already over when you return, which is also what happens with sleep.
| Status | Meaning |
|---|---|
0 |
The wait finished. |
1 |
Bad option or unparseable duration. |
128+N |
Killed by signal N — 130 for Ctrl-C, 143 for SIGTERM. |
Everything the display writes goes to stderr, which keeps stdout clean for pipelines and still shows the bar when stdout is redirected somewhere else.
When stderr is a pipe, a file, or a terminal that cannot handle cursor movement, the display switches to one line at the start and one at the end. Log files stay readable that way.
Colour follows the NO_COLOR convention, and --no-color turns it off for a single run. Terminal width comes from the terminal itself, falling back to $COLUMNS and then to 80 columns; -w overrides all of it.
| Variable | Effect |
|---|---|
SLEEPGO_PROGRESS |
Set to 1 to make --progress the default. |
NO_COLOR |
Any value disables colour. |
COLUMNS |
Terminal width, used when the terminal does not report one. |
TERM |
TERM=dumb selects the plain line output. |
Operand handling matches GNU coreutils, including the corners:
- Fractions, exponents and hex all parse the way C's
strtodreads them, so0.25,1234e-3and0x10are accepted, and0x1dis 29 seconds rather than one day. nan, negative numbers and trailing junk are rejected with the sameinvalid time intervalmessage.-1is reported as an unknown option, which is what GNU's option parser does with it.infandinfinitywait forever.
Every operand in the table above was diffed against GNU coreutils 8.30, and the two agree on all of them.
One difference worth knowing: a job suspended with Ctrl-Z is reported by the shell as stopped by SIGSTOP (147) rather than SIGTSTP (148). Go programs cannot re-raise SIGTSTP and reliably stop, so sleepgo raises the signal that always works.
Waiting is anchored to a single deadline taken at the start, so drawing a progress bar does not make the wait longer. Timing for a one second request:
sleep 1 1004 ms
sleepgo 1 1005 ms
sleepgo -p 1 1005 ms (twenty screen updates during the wait)
The deadline uses a monotonic clock. Clock changes during the wait — an NTP correction, a suspend and resume, someone running date — leave the length of the wait alone.
A silent sleepgo wakes up once. A countdown wakes once per second, timed to the moment the digit changes. A progress bar wakes roughly four times per bar cell, between 20 times a second and once a second, so a long wait stays cheap.
Most of the cost of sleep 0.01 in a tight shell loop is starting the process, and a Go binary starts more slowly than a C one:
/bin/true 0.41 ms per spawn
sleep 0 0.47 ms per spawn
sleepgo 0 0.90 ms per spawn
Half a millisecond is nothing for sleepgo 30 and it is the entire cost for sleepgo 0.001 in a loop. If you are spawning thousands of tiny sleeps, do the waiting inside one long-lived process instead. For everything else — the waits where a progress bar is worth having — it disappears into the noise.
go build -o sleepgo .
go test ./...
go test -race ./...The test suite covers duration parsing against the forms GNU accepts and rejects, option parsing, bar rendering from 20 to 200 columns, and the waiting itself: that it waits long enough, that ticking for the display does not push it past the deadline, and that it can be interrupted and suspended correctly.
Screenshots in this README are captured from real terminal sessions rather than assembled by hand.
Linux, macOS and the BSDs get everything, and Linux is what the project is tested on. Windows compiles and sleeps correctly; terminal width there comes from $COLUMNS, and the Ctrl-Z handling has no Windows equivalent so it is skipped. The Windows display has had no real-world testing, and reports are welcome.







