At a glance
;alerts after success and failure;&&only after success.$?holds the last exit status, and 0 means success.- Over SSH, use
curlto ntfy or a webhook instead of notify-send. - Already running?
tail --pid=PID -f /dev/nullwaits for it.
Desktop popup with notify-send
Add notify-send after your command to get a desktop popup when it finishes. The syntax is notify-send [-u low|normal|critical] [-t ms] [-a app] [-i icon] "summary" "body", and it talks to the notification service over your desktop session's D-Bus.
How you join the two commands matters. With ; the popup appears whatever happened, && fires only after success (exit status 0) and || only after a failure. $? holds the exit status of the last command, so "exit $?" in the body tells you whether it worked before you switch windows.
make build; notify-send "Build finished" "exit $?"
make build && notify-send "Build passed"
make build || notify-send -u critical "Build failed"A few flags worth knowing:
-usets the urgency:low,normalorcritical.-tsets how long the popup stays, in milliseconds, but GNOME Shell ignores it.-asets the app name and-ian icon.
Typing that every time gets old. Put a small function in ~/.bashrc or ~/.zshrc that runs any command, keeps its exit status and passes it to the popup.
notify_done() {
"$@"
local status=$?
notify-send "Finished: $*" "exit $status"
return $status
}
notify_done make buildAlready running? Wait for the PID
You started a backup 20 minutes ago and forgot the notification. Open a second terminal, find the process ID with pgrep -f backup.sh, and wait for it from there.
The bash builtin wait doesn't work for this: it only waits for children of the current shell. From another terminal it fails with wait: pid 12345 is not a child of this shell and exit status 127.
Use tail --pid=PID -f /dev/null from GNU coreutils, which exits as soon as that process is gone. Without GNU tail, poll with kill -0 in a loop. Neither way can see the other process's exit status, only that it ended.
tail --pid=12345 -f /dev/null; notify-send "Process 12345 ended"
while kill -0 12345 2>/dev/null; do sleep 5; done; notify-send "Process 12345 ended"Terminal bell and macOS
If you only want the terminal to get your attention, printf '\a' rings the bell. What that does, a sound, a flashing tab or a dock badge, depends on your terminal settings. Because it is just a character in the output, it reaches your local window over SSH too.
macOS has no notify-send. Use osascript to show a Notification Center banner, or say to have the Mac read the result out loud.
./backup.sh; printf '\a'
./backup.sh; osascript -e 'display notification "Done" with title "backup.sh"'
./backup.sh; say "backup finished"Over SSH: send it to your phone
On a remote server over plain SSH there is usually no notification service to talk to, so notify-send exits with status 1 and an error such as Failed to show notification: Cannot autolaunch D-Bus without X11 $DISPLAY.
Send the alert over HTTPS instead, and run long jobs inside tmux so a dropped connection doesn't kill the job before it reaches the notification. ntfy needs only curl and a topic name; topics on ntfy.sh are public and capped at 250 messages a day, so pick a long random name. A Discord webhook takes JSON with content up to 2,000 characters.
./backup.sh; curl -s -H "Title: backup.sh finished" -d "exit $?" ntfy.sh/your-long-random-topic
./backup.sh; status=$?
curl -s -H "Content-Type: application/json" \
-d "{\"content\": \"backup.sh finished with exit $status\"}" \
"$DISCORD_WEBHOOK_URL"For a single Python script, run PYTHONUNBUFFERED=1 tqdml run -- python job.py so its output reaches the live page as it prints.
Which method works where
| Method | Works over SSH | Fires on failure | Notes |
|---|---|---|---|
cmd; notify-send | No | Yes | Needs the desktop D-Bus; put $? in the body |
cmd && notify-send | No | No | Only after exit status 0; add || for failures |
printf '\a' | Yes | Yes, with ; | Sound, flash or badge depends on the terminal |
osascript | No | Yes, with ; | Notification Center banner on the Mac that runs it |
curl to ntfy | Yes | Yes, with ; | 250 messages a day on ntfy.sh; public topics |
curl to a Discord webhook | Yes | Yes, with ; | JSON content up to 2,000 characters |
tqdml run with TQDMLIVE_NOTIFY | Yes | Yes | Live output page; exit code and duration to Discord or Slack |
FAQ
What is the difference between ; and && before notify-send?
A semicolon runs notify-send when the first command ends, whether it worked or not. && runs it only after exit status 0, and || only after a non-zero exit status.
Why does notify-send not work over SSH?
notify-send talks to the notification service over a desktop session D-Bus. A plain SSH session usually has none, so it exits with status 1 and an error such as Failed to show notification: Cannot autolaunch D-Bus without X11 $DISPLAY. Use curl to ntfy or a webhook instead.
How do I get notified when an already running process finishes?
Find its PID with pgrep -f, then run tail --pid=PID -f /dev/null followed by your notification command. Without GNU tail, loop on kill -0 PID. The bash wait builtin only works for children of the same shell.
How do I get a notification on macOS when a command finishes?
Append osascript -e 'display notification "Done" with title "Job"' to show a Notification Center banner when the command ends. For an audio alert, append say with the text to read out.
Can the notification include the exit code?
Yes. The shell keeps the last exit status in $?, where 0 means success. Join your job and the notification with ; and put $? inside the double-quoted message.
Check on long runs from your phone
Change one import and every tqdm bar prints a link you can open on any device. Get a Discord or Slack message when the run finishes, fails or stalls.
pip install tqdm-liveSources
- notify-send(1) manual man.archlinux.org
- tail(1) manual: --pid man7.org
- Apple: Displaying notifications with AppleScript developer.apple.com
- ntfy docs: Publishing docs.ntfy.sh
- Discord developer docs: Execute Webhook docs.discord.com