Bash не перехватывает прерывания во время операторов exec rsync/subshell
Вопрос
Контекст:
У меня есть сценарий bash, который содержит подоболочку и ловушку для псевдосигнала EXIT, и он неправильно перехватывает прерывания во время rsync
.Вот пример:
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT
(
#main script logic, including the following lines:
(exec sleep 10;);
(exec rsync --progress -av --delete $directory1 /var/tmp/$directory2;);
) | 2>&1 tee -a $logfile
trap - EXIT #just in case cleanup isn't called for some reason
Идея сценария такова:большая часть важной логики выполняется в подоболочке, которая передается по конвейеру tee
и в файл журнала, поэтому мне не нужно tee
каждую строку основной логики, чтобы все это было зарегистрировано.Всякий раз, когда подоболочка завершается или скрипт по какой-либо причине останавливается (псевдосигнал EXIT должен фиксировать все эти случаи), ловушка перехватит его и запустит cleanup()
функцию, а затем удалите ловушку.А rsync
и sleep
команды (сон – это всего лишь пример) выполняются exec
чтобы предотвратить создание процессов-зомби, если я убью родительский скрипт во время их работы, и каждая потенциально долго выполняющаяся команда будет заключена в свою собственную подоболочку, так что при exec
завершается, это не приведет к завершению всего сценария.
Проблема:
Если я прерву сценарий (через kill
или CTRL+C) во время завершения выполнения/подоболочки sleep
Команда, ловушка работает должным образом, и я вижу «Очистка!» эхо и зарегистрировано.Если я прерву сценарий во время rsync
команда, я вижу rsync
закончить и написать rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
на экран, а затем скрипт просто умирает;никакой очистки, никаких ловушек.Почему прерывание/убийство rsync
запустить ловушку?
Я пробовал использовать --no-detach
переключился с помощью rsync, но это ничего не изменило.У меня bash 4.1.2, rsync 3.0.6, centOS 6.2.
Решение
Как насчет того, чтобы просто перенаправить весь вывод из точки X в tee без необходимости повторять его повсюду и возиться со всеми подоболочками и исполнителями...(надеюсь, я ничего не пропустил)
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
exec > >(exec tee -a $logfile) 2>&1
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap cleanup EXIT
sleep 10
rsync --progress -av --delete $directory1 /var/tmp/$directory2
Другие советы
В дополнение к set -e
, я думаю, ты хочешь set -E
:
Если этот параметр установлен, любая ловушка ERR наследуется функциями оболочки, подстановками команд и командами, выполняемыми в среде подоболочки.В таких случаях ловушка ERR обычно не наследуется.
Альтернативно, вместо того, чтобы заключать команды в подоболочки, используйте фигурные скобки, которые по-прежнему дадут вам возможность перенаправлять выходные данные команд, но будут выполнять их в текущей оболочке.
Прерывание будет правильно перехвачено, если вы добавите в ловушку INT.
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash правильно перехватывает прерывания.Однако это не отвечает на вопрос, почему скрипт прерывается при выходе, если sleep
прерывается, а также почему он не запускается rsync
, но заставляет скрипт работать должным образом.Надеюсь это поможет.
Ваша оболочка может быть настроена на выход при ошибке:
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
Если ты прервешь sleep
(^C), то подоболочка завершится из-за set -e
и распечатать woah
в процессе.
И еще немного не по теме:твой trap - EXIT
находится в подоболочке (явно), поэтому не будет иметь эффекта после возврата функции очистки
Из экспериментов довольно ясно, что rsync
ведет себя как другие инструменты, такие как ping
и не наследовать сигналы от вызывающего родителя Bash.
Поэтому вам придется подойти к этому немного творчески и сделать что-то вроде следующего:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
Теперь, когда я запускаю его:
$ ./rsync.bash
X11 forwarding request failed
building file list ... done
LargeTestFile.500M
^C^C^C^C^C^C^C^C^C^C
sent 509984 bytes received 42 bytes 92732.00 bytes/sec
total size is 524288000 speedup is 1027.96
FIN
И мы видим, что файл полностью перенесся:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
Как это работает
Хитрость здесь в том, что мы сообщаем Bash через set -m
чтобы отключить управление заданиями для любых фоновых заданий внутри него.Затем мы создаем фон rsync
а затем запускаем wait
команда, которая будет ждать последней команды запуска, rsync
, пока он не будет завершен.
Затем мы охраняем весь скрипт с помощью trap '' SIGINT SIGTERM EXIT
.