Notify-send doesn't work from crontab

I made a script that should notify me when there's a new chapter of manga that I'm reading. I used the command notify-send to do this. The program works when I am trying to run it in terminal. The notification is showing. However, when I placed this in my crontab, the notification doesn't show. I'm pretty sure that the program is running since I made it to create a file for me. The file was created, but the notification didn't show.

Here's my script

#!/bin/bash
#One Piece Manga reminder
#I created a file named .newop that contains the latest chapter.
let new=$(cat ~/.newop)
wget --read-timeout=30 -t20 -O .opreminder.txt
if (( $(cat .opreminder.txt | grep "One Piece $new" | wc -l) >=1 ))
then (( new+=1 )) echo $new echo $new > ~/.newop notify-send "A new chapter of One Piece was released."
else notify-send "No new chapter for One Piece." notify-send "The latest chapter is still $new."
fi
exit

And here's what I wrote in my crontab

0,15,30,45 12-23 * * 3 /home/jchester/bin/opreminder.sh
5

20 Answers

Things seem to be different on 13.04, at least in Gnome Shell.

First, this is what env prints when run from user zzyxy's (not root's) cron job:

HOME=/home/zzyxy
LOGNAME=zzyxy
PATH=/usr/bin:/bin
XDG_RUNTIME_DIR=/run/user/zzyxy
LANG=en_US.UTF-8
SHELL=/bin/sh
PWD=/home/zzyxy

To get notify-send to work, it seems to be necessary to set the DBUS_SESSION_BUS_ADDRESS environment variable, as per DahitiF's comment on ubuntuforums.org. Just prepend the following to your actual job description:

eval "export $(egrep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $LOGNAME gnome-session)/environ)";

It doesn't seem to be necessary to set DISPLAY.

7

Command notify-send would not show the message on your screen when started by cron. Just add target display at the top of your script, for example:

export DISPLAY=:0
4

Commands need to reference their location. So notify-send needs to be /usr/bin/notify-send

All commands need to have their full path.

Use the whereis notify-send command to see where your commands "live"

8

I use i3 on Ubuntu 18.04 and 20.04. My way to solve this is:

* * * * * XDG_RUNTIME_DIR=/run/user/$(id -u) notify-send Hey "this is dog!"

3

For Ubuntu 14.04 at least, klrmr's response above is the correct answer. It does not appear to be necessary to set DISPLAY or articulate full paths for notify-send or anything otherwise normally in $PATH.

Below is a cron script I'm using to shutdown a virtual machine when a laptop's battery state becomes too low. The line setting DBUS_SESSION_BUS_ADDRESS in klrmr's response above is the modification that finally got the warnings working correctly.

#!/bin/bash
# if virtual machine is running, monitor power consumption
if pgrep -x vmware-vmx; then bat_path="/sys/class/power_supply/BAT0/" if [ -e "$bat_path" ]; then bat_status=$(cat $bat_path/status) if [ "$bat_status" == "Discharging" ]; then bat_current=$(cat $bat_path/capacity) # halt vm if critical; notify if low if [ "$bat_current" -lt 10 ]; then /path/to/vm/shutdown/script echo "$( date +%Y.%m.%d_%T )" >> "/home/user/Desktop/VM Halt Low Battery" elif [ "$bat_current" -lt 15 ]; then eval "export $(egrep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $LOGNAME gnome-session)/environ)"; notify-send -i "/usr/share/icons/ubuntu-mono-light/status/24/battery-caution.svg" "Virtual machine will halt when battery falls below 10% charge." fi fi fi
fi
exit 0
2

In my case with ubuntu 16.04 any explicit path was required, I solve the problem just adding

DISPLAY=:0

at firsts lines of the crontab, before call notify-send.

1

This took forever to make work on ubuntu 15.10, Had to add a source to get the users normal env vars. my display was :1 for some reason as well. Using the gnome-session first results pid for DBUS_SESSION_BUS_ADDRESS lookup.

# Crontab is
* 21 * * * /bin/sh /home/tristik/cron.sh
#!/bin/sh
# cron.sh
# Notifies the user of date and time
source /home/tristik/.bashrc
pid=$(pgrep -u tristik gnome-session | head -n 1)
dbus=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$pid/environ | sed 's/DBUS_SESSION_BUS_ADDRESS=//' )
export DBUS_SESSION_BUS_ADDRESS=$dbus
export HOME=/home/tristik
export DISPLAY=:1
/usr/bin/notify-send 'title' "$(/bin/date)"
1

First culprit is your crontab file, you also need to mention the user name with which the script has to be executed, better keep it as root

0,15,30,45 12-23 * * 3 root /home/jchester/bin/opreminder.sh

and then you should use the user_name of the GUI user inside the script and prepend it to notify-send with "sudo or su" to execute the command as a user who owns the GUI

example :

su gnome_user_name -c 'notify-send "summary" "body"'

or

sudo -u gnome_user_name notify-send "summary" "body"

where gnome_user_name is the username of the user who started the GUI session it is you who logged in, and if you want to make it a dynamic pick, you can get it from

GNOME_USER=`ps -eo uname,cmd | grep gnome-session| head -1 | cut -d' ' -f1 `

example :

su $GNOME_USER -c 'notify-send "summary" "body"'

or

sudo -u $GNOME_USER notify-send "summary" "body"
3

The way the binary retrieves the dbus address seems to have changed lately. On Ubuntu 15.04 (Vivid Vervet) with "notify-send 0.7.6", the following two variables are needed:

export HOME=/home/$notify_user
export DISPLAY=:0.0

The statement by 'krlmlr' evaluates fine and sets the correct address, but the dialog won't pop up from a cron job.

Use printenv for print environment variables from your normal terminal. And then paste all environment variables in starting of crontab file.

1

If your script in crontab is running as root, the answers above will probably not work. Try this function, which works fine for me in 16.04 :

notify_all() { local title=$1 local msg=$2 who | awk '{print $1, $NF}' | tr -d "()" | while read u d; do id=$(id -u $u) . /run/user/$id/dbus-session export DBUS_SESSION_BUS_ADDRESS export DISPLAY=$d su $u -c "/usr/bin/notify-send '$title' '$msg'" done
}

( Source: )

Better to rely on dbus-session process, it should be running for all systems where DBUS_SESSION_BUS_ADDRESS is present.

Create a script:

#!/bin/bash
# notify.sh
environs=`pidof dbus-daemon | tr ' ' '\n' | awk '{printf "/proc/%s/environ ", $1}'`
export DBUS_SESSION_BUS_ADDRESS=`cat $environs 2>/dev/null | tr '\0' '\n' | grep DBUS_SESSION_BUS_ADDRESS | cut -d '=' -f2-`
export DISPLAY=:0
notify-send "It works!"

Make it executable:

$ chmod +x ~/notify.sh

Add it to crontab:

* * * * * $HOME/notify.sh
1

I have just got this to work with the cinnamon desktop on Ubuntu 15.10, using the following recipe:

if [ ! -v DBUS_SESSION_BUS_ADDRESS ]; then pid=$(pgrep -u $LOGNAME cinnamon-sessio) eval "export $(\grep -z DBUS_SESSION_BUS_ADDRESS /proc/$pid/environ)"
fi
notify-send "$RESUME" "$INFO"

The trick was to realize that 'cinnamon-session' is too long for pgrep to find:

$ pgrep -u $LOGNAME cinnamon-session
$ pgrep -u $LOGNAME cinnamon
30789
30917
30965
30981
31039
31335
$ ps -a | \grep cinnamon
30789 tty2 00:00:00 cinnamon-sessio
30917 tty2 00:00:02 cinnamon-settin
30965 tty2 00:00:00 cinnamon-launch
30981 tty2 00:04:15 cinnamon
31039 tty2 00:00:00 cinnamon-killer
31335 tty2 00:00:00 cinnamon-screen
$ ps a | \grep cinnamon 4263 pts/1 S+ 0:00 grep cinnamon
30779 tty2 Ssl+ 0:00 /usr/lib/gdm/gdm-x-session --run-script cinnamon-session-cinnamon
30789 tty2 Sl+ 0:00 cinnamon-session --session cinnamon
30917 tty2 Sl+ 0:02 /usr/lib/x86_64-linux-gnu/cinnamon-settings-daemon/cinnamon-settings-daemon
30965 tty2 Sl+ 0:00 /usr/bin/python2 /usr/bin/cinnamon-launcher
30970 tty2 Sl+ 0:00 /usr/lib/x86_64-linux-gnu/cinnamon-settings-daemon/csd-printer
30981 tty2 Sl+ 4:16 cinnamon --replace
31039 tty2 Sl+ 0:00 /usr/bin/python2 /usr/bin/cinnamon-killer-daemon
31335 tty2 Sl+ 0:00 cinnamon-screensaver
$ pgrep -u $LOGNAME cinnamon-sessio
30789

I also had to use \grep because my grep is aliased to

$ alias grep
alias grep='grep -n --color=always'

Issue caused by calling python3 in crontab with UTF-8 locale.

TL;DR: prefix call in crontab w/ locale as in:

*/5 * * * * LC_ALL=en_US.utf-8 LANG=en_US.utf-8 ~/.local/bin/watson-notify

See also click and python3:

Traceback (most recent call last): File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "/usr/lib/python3.6/runpy.py", line 85, in _run_code exec(code, run_globals) File "/usr/lib/python3/dist-packages/watson/__main__.py", line 6, in <module> cli.cli() File "/usr/lib/python3/dist-packages/click/core.py", line 759, in __call__ return self.main(*args, **kwargs) File "/usr/lib/python3/dist-packages/click/core.py", line 693, in main _verify_python3_env() File "/usr/lib/python3/dist-packages/click/_unicodefun.py", line 123, in _verify_python3_env 'for mitigation steps.' + extra)
RuntimeError: Click will abort further execution because Python 3 was configured to use ASCII as encoding for the environment. Consult for mitigation steps.
This system supports the C.UTF-8 locale which is recommended.
You might be able to resolve your issue by exporting the
following environment variables: export LC_ALL=C.UTF-8 export LANG=C.UTF-8

For all crontab script that use libnotify, I use this:

notify_user() { local user=$(whoami) notify-send -u normal -t 4000 "System Backup" "Starting backup"
}
notify_user # and do other stuff

It works even if I use cron in root mode.

All you need is the X_user and X_userid. Replace both in the command bellow.

Solution with systemd

/etc/systemd/system/opreminder.service #Service file

[Unit]
Descrption=some service to run
[Service]
User=[X_user]
ExecStart=/home/jchester/bin/opreminder.sh

/etc/systemd/system/opreminder.timer #timer file

[Unit]
Description=Some desc
[Timer]
OnCalendar=0,15,30,45 12-23 * * 3
[Install]
WantedBy=list.timer.target

/home/jchester/bin/opreminder.sh #The script

#!/usr/bin/env bash
sudo -u [X_user] DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/[X_userid]/bus notify-send 'Hello world!' 'This is an example notification.'

No need to use sudo -u if the service file is already set with the intended user

Source:

This makes it work on 19.10:

eval "export $(pgrep -u $LOGNAME gnome-session | head -n 1 | xargs -I{} cat /proc/{}/environ | egrep -z DBUS_SESSION_BUS_ADDRESS)";

In the case below, I was calling notify-send from a python script that monitors memory of processes as I've been having issues with XOrg memory growth.

The example below should work and does not output the warning: command substitution: ignored null byte in input warning.

myscript_cron.sh:

#!/bin/bash
echo $0 called: `date`
export USER=`whoami`
export HOME=/home/$USER
export DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u ${USER} gnome-session | head -n 1)/environ | tr '\0' '\n'| sed 's/DBUS_SESSION_BUS_ADDRESS=//')
cd <path_to_my_script>
/usr/bin/python3.7 ./<my_script>.py 2>&1 1>/dev/null

crontab:

Note: Ran as crontab -e in my user account and not sudo

* * * * * <path_to_my_script>/myscript_cron.sh >> <path_to_my_script>/cron.log 2>&1

I like to monitor output of cron calls to a log file so I can debug the shell script but I don't want it to be polluted with the stdout of the python script. I output the python script to a separate log.

For whoever is using the fish shell, like me, here is my script which essentially does the same as the bash script of @denis.peplin. I use it to send a warning when the battery charge is low.

#!/bin/fish
# set environment variables
pidof dbus-daemon | tr ' ' '\n' | while read -l pid set environs $environs /proc/$pid/environ
end
set -x DBUS_SESSION_BUS_ADDRESS (cat $environs 2>/dev/null | tr '\0' '\n' | grep DBUS_SESSION_BUS_ADDRESS>
set -x DISPLAY :0
# perform actual job
set capacity (cat /sys/class/power_supply/BAT0/capacity)
if test $capacity -le $argv[1] /usr/local/bin/dunstify -u critical -t 10000 "Battery low!" "Plug in asap."
end

I had to avoid awk, because its return type is a string containing two paths that are separated by a space. A fish variable does not extract the two paths, but stores the whole string, causing conflicts when using cat $environs. Using a while loop that appends the paths to the variable achieves the intended.

The solution with setting DISPLAY=:0.0 worked for me for years. In 20.04, it suddenly stopped working. It turned out that now the display coordinates changed, it's now :1. So

export DISPLAY=:1

Solved the problem.

The styling of the popup is ugly, but it's a different story.

Update

What I wrote above was about the case of upgrading from 16.04 via 18.04 to 20.04.

But when I reinstalled Ubuntu 20.04 from scratch (still keeping /home/$USER), it stopped working (probably, notify-send is now implemented by a different program).

So, now the DISPLAY variable is not relevant, but here is what I need to define: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus.

So a line in crontab could look like this:

14 * * * * DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send 'Hello!'

The pop-up has the same styling as when running notify-send from a terminal emulator.

You Might Also Like