Notify-send не работает из crontab

Вопрос или проблема

Я написал скрипт, который должен уведомлять меня, когда выходит новая глава манги, которую я читаю. Я использовал команду notify-send для этого. Программа работает, когда я пытаюсь запустить ее в терминале. Уведомление отображается. Однако, когда я добавил это в мой crontab, уведомление не отображается. Я почти уверен, что программа выполняется, так как я заставил ее создать файл для меня. Файл был создан, но уведомление не отобразилось.

Вот мой скрипт

#!/bin/bash   
# Напоминание о манге Ван Пис    
# Я создал файл с именем .newop, который содержит последнюю главу.    
let new=$(cat ~/.newop)    
wget --read-timeout=30 -t20 -O .opreminder.txt http://www.mangareader.net/103/one-piece.html

if (( $(cat .opreminder.txt | grep "One Piece $new" | wc -l) >=1 ))    
then    
    (( new+=1 ))    
    echo $new    
    echo $new > ~/.newop    
    notify-send "Вышла новая глава Ван Пис."    
else    
    notify-send "Нет новой главы Ван Пис."    
    notify-send "Последняя глава все еще $new."    
fi        
exit

И вот что я написал в моем crontab

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

Похоже, что ситуация изменилась в 13.04, по крайней мере в Gnome Shell.

Во-первых, вот что env выводит, когда запускается из задания cron пользователя zzyxy (не root):

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

Чтобы notify-send работал, кажется, необходимо установить переменную окружения DBUS_SESSION_BUS_ADDRESS, согласно комментарию DahitiF на ubuntuforums.org. Просто добавьте следующее в описание вашей задачи:

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

Устанавливать DISPLAY не нужно.

Команда notify-send не отображала сообщение на вашем экране, когда запускалась cron. Просто добавьте целевой дисплей вверху вашего скрипта, например:

export DISPLAY=:0

Команды должны ссылаться на свое местоположение. Поэтому notify-send должен быть /usr/bin/notify-send.

Все команды должны иметь полный путь.

Используйте команду whereis notify-send, чтобы увидеть, где находятся ваши команды.

Я использую i3 на Ubuntu 18.04 и 20.04. Мой способ решения этой проблемы:

* * * * * XDG_RUNTIME_DIR=/run/user/$(id -u) notify-send Привет "это собака!"

По крайней мере для Ubuntu 14.04, ответ klrmr выше является правильным. Не кажется, что необходимо устанавливать DISPLAY или указывать полные пути для notify-send или чего-либо другого, что обычно находится в $PATH.

Ниже приведен скрипт cron, который я использую для завершения работы виртуальной машины, когда уровень заряда батареи ноутбука становится слишком низким. Строка, устанавливающая DBUS_SESSION_BUS_ADDRESS в ответе klrmr выше, является модификацией, которая наконец заставила предупреждения работать корректно.

#!/bin/bash

# если виртуальная машина работает, отслеживаем потребление энергии
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)
      # остановить vm, если критично; уведомить, если низкий уровень
      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"  "Виртуальная машина будет остановлена, когда заряд батареи упадет ниже 10%."   
      fi
    fi
  fi
fi

exit 0

В моем случае с ubuntu 16.04 требовался явный путь, я решил проблему, просто добавив

DISPLAY=:0

в первые строки crontab, перед вызовом notify-send.

Я долго мучился с этим в ubuntu 15.10. Пришлось добавить источник, чтобы получить обычные переменные окружения пользователя. По какой-то причине мой дисплей был :1. Использовал PID первой сессии gnome-session для поиска адреса DBUS_SESSION_BUS_ADDRESS.

# Crontab:
* 21 * * * /bin/sh /home/tristik/cron.sh
#!/bin/sh 
# cron.sh
# Уведомляет пользователя о дате и времени
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)"

Первая причина – это ваш файл crontab, вам также нужно указать имя пользователя, с которым должен выполняться скрипт, лучше оставлять его как root

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

А затем вы должны использовать имя пользователя графического интерфейса внутри скрипта и предварительно указать его для notify-send с помощью “sudo или su”, чтобы выполнить команду от имени пользователя, который владеет графическим интерфейсом

пример:

su gnome_user_name -c 'notify-send "сводка" "тело"'

или

sudo -u gnome_user_name notify-send "сводка" "тело"

где gnome_user_name – это имя пользователя, который запустил визуальную сессию, то есть вы, кто вошел в систему, и если вы хотите сделать его динамическим, вы можете получить его от

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

пример:

su $GNOME_USER -c 'notify-send "сводка" "тело"'

или

sudo -u $GNOME_USER notify-send "сводка" "тело"

Способ получения адреса dbus, похоже, изменился в последнее время. На Ubuntu 15.04 (Vivid Vervet) с “notify-send 0.7.6” требуются следующие две переменные:

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

Заявление ‘krlmlr’ выполняется правильно и устанавливает правильный адрес, но диалог не появляется из задания cron.

В следующем случае я вызывал notify-send из python-скрипта, который отслеживает память процессов, так как у меня были проблемы с ростом памяти XOrg.

Следующий пример должен работать и не выводить предупреждение warning: command substitution: ignored null byte in input.

myscript_cron.sh:

#!/bin/bash
echo $0 вызван: `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:

Примечание: Запущен как crontab -e в моей учетной записи пользователя, а не sudo

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

Мне нравится отслеживать вывод вызовов cron в файл журнала, чтобы я мог отлаживать оболочку скрипта, но я не хочу, чтобы он был загрязнен стандартным выводом python-скрипта. Я вывожу python-скрипт в отдельный журнал.

Используйте printenv для вывода переменных окружения из вашего обычного терминала. А затем вставьте все переменные окружения в начале файла crontab.

Если ваш скрипт в crontab выполняется от имени root, то ответы выше, вероятно, не сработают. Попробуйте эту функцию, которая отлично работает для меня в 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 
}

( Источник: https://unix.stackexchange.com/a/344377/7286 )

Лучше полагаться на процесс dbus-session, он должен работать для всех систем, в которых присутствует DBUS_SESSION_BUS_ADDRESS.

Создайте скрипт:

#!/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 "Это работает!"

Сделайте его исполняемым:

$ chmod +x ~/notify.sh

Добавьте его в crontab:

* * * * * $HOME/notify.sh

Я только что заставил это работать с рабочим столом cinnamon на Ubuntu 15.10, используя следующий рецепт:

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"

Хитрость заключалась в том, что ‘cinnamon-session’ слишком длинный для pgrep, чтобы найти:

$ 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

Мне также пришлось использовать \grep, потому что мой grep был алиасирован в

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

Проблема вызвана вызовом python3 в crontab с локализацией UTF-8.

Кратко: добавьте префикс к вызову в crontab с локалью, как в:

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

Смотрите также click и 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
    'для шагов по смягчению.' + extra)
RuntimeError: Click будет завершен, поскольку Python 3 был настроен на использование ASCII в качестве кодировки для окружения. Обратитесь к http://click.pocoo.org/python3/ для шагов по смягчению.

Эта система поддерживает локаль C.UTF-8, которая рекомендуется. Вы можете разрешить вашу проблему, экспортировав следующие переменные окружения:

    export LC_ALL=C.UTF-8
    export LANG=C.UTF-8

Для всех скриптов crontab, использующих libnotify, я использую это:

notify_user() {
    local user=$(whoami)
    notify-send -u normal -t 4000 "Резервное копирование системы" "Начало резервного копирования"
}

notify_user # и делай другие дела

Это работает даже если я использую cron в режиме root.

Все, что вам нужно, это X_user и X_userid. Замените оба в команде ниже.

Решение с systemd

/etc/systemd/system/opreminder.service # Файл службы

[Unit]
Description=Некоторая служба для исполнения

[Service]
User=[X_user]
ExecStart=/home/jchester/bin/opreminder.sh

/etc/systemd/system/opreminder.timer # Файл таймера

[Unit]
Description=Некоторое описание

[Timer]
OnCalendar=0,15,30,45 12-23 * * 3 

[Install]
WantedBy=list.timer.target

/home/jchester/bin/opreminder.sh # Скрипт

#!/usr/bin/env bash

sudo -u [X_user] DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/[X_userid]/bus notify-send 'Привет, мир!' 'Это пример уведомления.'

Нет необходимости использовать sudo -u, если файл службы уже установлен с нужным пользователем.

Источник: https://wiki.archlinux.org/index.php/Desktop_notifications#Usage_in_programming

Это заставило это работать на 19.10:

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

Для тех, кто использует fish shell, как я, вот мой скрипт, который по сути делает то же самое, что и bash-скрипт @denis.peplin. Я использую его, чтобы отправлять предупреждение, когда заряд батареи низкий.

#!/bin/fish

# Установка переменных окружения
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

# Выполняем фактическую задачу
set capacity (cat /sys/class/power_supply/BAT0/capacity)
if test $capacity -le $argv[1]
        /usr/local/bin/dunstify -u critical -t 10000 "Батарея низкая!" "Подключите зарядное устройство как можно скорее."
end

Мне пришлось избегать awk, потому что его тип возвращаемых данных – это строка, содержащая два пути, отделенных пробелом. Переменная fish не извлекает два пути, а сохраняет всю строку, что вызывает конфликты при использовании cat $environs. Использование цикла while, который добавляет пути в переменную, достигает цели.

Решение с установкой DISPLAY=:0.0 работало для меня много лет. В 20.04 это неожиданно перестало работать. Оказалось, что теперь координаты дисплея изменились, теперь это :1. Поэтому

export DISPLAY=:1

Проблема решена.

Оформление всплывающего окна некрасивое, но это другая история.

Обновление

То, что я написал выше, касалось случая обновления с 16.04 через 18.04 до 20.04.

Но когда я переустанавливал Ubuntu 20.04 с нуля (по-прежнему сохраняя /home/$USER), это перестало работать (вероятно, notify-send теперь реализован другой программой).

Теперь переменная DISPLAY не актуальна, но вот что я должен определить: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus.

Таким образом, строка в crontab может выглядеть так:

14 * * * *     DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send 'Привет!'

Всплывающее окно имеет то же оформление, что и при запуске notify-send из терминального эмулятора.

В моем случае проблема заключалась в использовании пользователя root с notify-send. Я понял это, когда увидел, что sudo notify-send не работал в терминале, но просто notify-send (т.е. с текущим пользователем) работал. Поэтому вместо редактирования обычного /etc/crontab, который используется root, я создал собственный cron-скрипт для myusername. Пожалуйста, проверьте мой полный ответ здесь.

Ответ или решение

Чтобы уведомления, отправляемые с помощью notify-send, работали из crontab, необходимо учитывать несколько важных моментов, связанных с окружением и контекстом выполнения скрипта.

Проблема: Когда вы запускаете скрипт напрямую из терминала, он выполняется в контексте пользовательского сеанса X (графического интерфейса пользователя), имея доступ к DBUS_SESSION_BUS_ADDRESS и DISPLAY, которые необходимы для корректного вывода уведомлений. Однако, при запуске из crontab, этот контекст может отсутствовать, что объясняет, почему уведомления не появляются.

Решение:

  1. Укажите полный путь к notify-send:
    Путь к команде notify-send может быть не распознан, если она не находится в пути окружения в контексте cron. Убедитесь, что вы используете полный путь, например:

    /usr/bin/notify-send "Заголовок" "Сообщение"
  2. Установите переменную окружения DISPLAY:
    В вашем скрипте перед вызовом notify-send добавьте строку для установки переменной DISPLAY. Зачастую это :0, но если у вас несколько сессий X, может понадобиться указать другой номер:

    export DISPLAY=:0
  3. Установите переменную окружения DBUS_SESSION_BUS_ADDRESS:
    Эта переменная необходима для взаимодействия с сеансом DBus. Вы можете получить ее значение из текущих процессов в системе:

    export DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $USER gnome-session)/environ | sed 's/DBUS_SESSION_BUS_ADDRESS=//')
  4. Пример вашего скрипта:
    Измените ваш скрипт, добавив все вышеперечисленные аспекты:

    #!/bin/bash
    export DISPLAY=:0
    export DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$(pgrep -u $USER gnome-session)/environ | sed 's/DBUS_SESSION_BUS_ADDRESS=//')
    
    # Здесь ваш код для проверки новой главы и использование notify-send
    let new=$(cat ~/.newop)
    wget --read-timeout=30 -t20 -O .opreminder.txt http://www.mangareader.net/103/one-piece.html
    
    if (( $(cat .opreminder.txt | grep "One Piece $new" | wc -l) >= 1 )); then
       (( new+=1 ))
       echo $new
       echo $new > ~/.newop
       /usr/bin/notify-send "Вышла новая глава One Piece."
    else
       /usr/bin/notify-send "Нет новой главы для One Piece."
       /usr/bin/notify-send "Последняя глава - $new."
    fi
  5. Обновите crontab:
    Убедитесь, что вы добавили команды корректно в ваш crontab. Например:

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

Дополнительные рекомендации:

  • Логи и отладка: Если уведомления все еще не работают, добавьте логирование в ваш скрипт, чтобы отслеживать выполнение. Например, вы можете использовать:

    echo "$(date): Выполняется уведомление" >> /tmp/opreminder.log
  • Проверка зависимостей: Убедитесь, что notify-send установлен и доступен для вашего пользователя, и что в системе работает нужный графический интерфейс.

Следуя этим рекомендациям, вы сможете успешно отправлять уведомления через notify-send, запущенные из crontab.

Оцените материал
Добавить комментарий

Капча загружается...