Вопрос или проблема
Я написал скрипт, который должен уведомлять меня, когда выходит новая глава манги, которую я читаю. Я использовал команду 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
, этот контекст может отсутствовать, что объясняет, почему уведомления не появляются.
Решение:
-
Укажите полный путь к
notify-send
:
Путь к командеnotify-send
может быть не распознан, если она не находится в пути окружения в контексте cron. Убедитесь, что вы используете полный путь, например:/usr/bin/notify-send "Заголовок" "Сообщение"
-
Установите переменную окружения
DISPLAY
:
В вашем скрипте перед вызовомnotify-send
добавьте строку для установки переменнойDISPLAY
. Зачастую это:0
, но если у вас несколько сессий X, может понадобиться указать другой номер:export DISPLAY=:0
-
Установите переменную окружения
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=//')
-
Пример вашего скрипта:
Измените ваш скрипт, добавив все вышеперечисленные аспекты:#!/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
-
Обновите
crontab
:
Убедитесь, что вы добавили команды корректно в вашcrontab
. Например:0,15,30,45 12-23 * * 3 /home/jchester/bin/opreminder.sh
Дополнительные рекомендации:
-
Логи и отладка: Если уведомления все еще не работают, добавьте логирование в ваш скрипт, чтобы отслеживать выполнение. Например, вы можете использовать:
echo "$(date): Выполняется уведомление" >> /tmp/opreminder.log
-
Проверка зависимостей: Убедитесь, что
notify-send
установлен и доступен для вашего пользователя, и что в системе работает нужный графический интерфейс.
Следуя этим рекомендациям, вы сможете успешно отправлять уведомления через notify-send
, запущенные из crontab
.