How to create desktop notifications and reminders from Linux terminal
Introduction
Sometimes it’s useful to get visual feedback from a script. For example when script or cron job completes. Or when long-running build fails. Or when there is urgent problem during script execution. Desktop applications can do this with popup notifications. But it can be done from script too! You can use script commands to send yourself desktop notifications and reminders.
The below code has been written and tested on Linux. What about MacOS? Well… it can be done too, with a bit of effort. See the last chapter for some hints and tips.
Sending notifications from Linux terminal
To send notification from Linux terminal, use notify-send command. Run which notify-send
to see if it’s present. If not, install it with your package manager of choice, for example
sudo apt install notify-send
A few examples of simple notifications:
notify-send "Dinner ready!"
notify-send "Tip of the Day" "How about a nap?"
You can customize the notification with options such as urgency level, custom icon etc. Find out more with man notify-send
. You can use a small set of HTML tags in notification body, to give your notifications nice touch. On top of that, URLs are rendered as clickable, for example:
notify-send -u critical \
"Build failed!" \
"There were <b>123</b> errors. Click here to see the results: http://buildserver/latest"
Sent notifications are picked up by desktop environment and displayed just like any other notification. They will have the same consistent look, feel and behaviour.
Combine notify-send with AT
We all know cron, used to schedule commands at regular intervals. Command at is used to schedule single execution of a command at a specified time. If you run it like this:
at 12:00
it will start in interactive mode, where you can enter commands to execute at given time. This isn’t useful for scripts. Luckily, at
accepts parameters from standard input, so we can use it this way:
echo "npm run build" | at now + 1 minute
echo "backup-db" | at 13:00
There are many ways of specifying time. From absolute time such as 10:00
through relative time such as now + 2 hours
to special times such as noon
or midnight
. We can combine it with notify-send
to show ourselves reminders at some time in future, for example:
echo "notify-send 'Stop it and go home now?' 'Enough work for today.' -u critical" | at now
Scheduled commands are executed by daemon called atd
. On some systems it isn’t running by default. You can use the systemctl command to enable the daemon and make it start automatically on system restart:
sudo systemctl enable --now atd
REMIND command
Now, let’s build a custom bash command for sending ourselves reminders. How about something as simple and human-friendly as:
remind "I'm still here" now
remind "Time to wake up!" in 5 minutes
remind "Dinner" in 1 hour
remind "Take a break" at noon
remind "It's Friday pints time!" at 17:00
This is better than Alexa! How to get this goodness?
See the code below. It defines a shell function called remind
which supports the above syntax. The actual work is done in the last two lines. The rest is responsible for help, parameter validation etc. which rougly matches the proportion of useful code vs necessary white-noise in any large application ;-)
Save the code somewhere, for example in ~/bin/remind
file and source the function in your .bashrc
profile, so that it’s loaded when you log in:
source ~/bin/remind
Reload the terminal, then type remind
to see the syntax. Enjoy!
#!/usr/bin/env bash
function remind () {
local COUNT="$#"
local COMMAND="$1"
local MESSAGE="$1"
local OP="$2"
shift 2
local WHEN="$@"
# Display help if no parameters or help command
if [[ $COUNT -eq 0 || "$COMMAND" == "help" || "$COMMAND" == "--help" || "$COMMAND" == "-h" ]]; then
echo "COMMAND"
echo " remind <message> <time>"
echo " remind <command>"
echo
echo "DESCRIPTION"
echo " Displays notification at specified time"
echo
echo "EXAMPLES"
echo ' remind "Hi there" now'
echo ' remind "Time to wake up" in 5 minutes'
echo ' remind "Dinner" in 1 hour'
echo ' remind "Take a break" at noon'
echo ' remind "Are you ready?" at 13:00'
echo ' remind list'
echo ' remind clear'
echo ' remind help'
echo
return
fi
# Check presence of AT command
if ! which at >/dev/null; then
echo "remind: AT utility is required but not installed on your system. Install it with your package manager of choice, for example 'sudo apt install at'."
return
fi
# Run commands: list, clear
if [[ $COUNT -eq 1 ]]; then
if [[ "$COMMAND" == "list" ]]; then
at -l
elif [[ "$COMMAND" == "clear" ]]; then
at -r $(atq | cut -f1)
else
echo "remind: unknown command $COMMAND. Type 'remind' without any parameters to see syntax."
fi
return
fi
# Determine time of notification
if [[ "$OP" == "in" ]]; then
local TIME="now + $WHEN"
elif [[ "$OP" == "at" ]]; then
local TIME="$WHEN"
elif [[ "$OP" == "now" ]]; then
local TIME="now"
else
echo "remind: invalid time operator $OP"
return
fi
# Schedule the notification
echo "notify-send '$MESSAGE' 'Reminder' -u critical" | at $TIME 2>/dev/null
echo "Notification scheduled at $TIME"
}
Script Notifications on MacOS
Although there is no notify-send
on MacOS, notifications can be sent with ActionScript. For example:
osascript -e 'display notification "Wake up!" with title "Reminder"'
You can find full documentation of display notification
command here. Also, there’s at
available, so things should be easy? Well, not so. Sadly, MacOS makes it increasingly difficult for power users to use their powers …
First, command at
is disabled by default. Even if you run it, nothing will happen at scheduled time, because atrun
daemon is disabled, and no user account is allowed to use it by default.
To allow yourself to use the command, edit /var/at/at.allow
file and add your user name:
sudo open -a textedit /var/at/at.allow
Then enable atrun
daemon:
sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist
Let’s try sending a notification:
osascript -e 'display notification "Wake up!" with title "Reminder"' | at now
… and nothing happens. Why?
The daemon is not allowed to interact with desktop, nor anything else by that matter. To fix this, go to System Preferences / Security & Privacy / Privacy / Full Disk Access and add atrun
to the list. The file is found at /usr/libexec/atrun
path. You want to see this:
… only how to select this file? MacOS won’t display system folders in the file selector, even if you’ve been asked to authenticate as admin just a minute ago. I managed to circumvent it by adding my /usr
folder to list of favourite folders in Finder:
Last-but-not-least, for some even this won’t work. AT will not send these pesky notifications, period. It has something to do with script executing not in user space. You can find more information and solution here: https://github.com/ChrisJohnsen/tmux-MacOSX-pasteboard. This is what helps:
# Install reattach-to-user-namespace utility
brew install reattach-to-user-namespace
# Run notification command as follows
reattach-to-user-namespace osascript -e 'display notification "Wake up!" with title "Reminder"' | at now
Phew. Hopefully it works for you too, and you can use remind
script on your Mac as well! Happy weekend!