
Контекст: У меня есть скрипт Bash, который копирует файлы
function log () {
read IN
if [ "$IN" == "" ]; then
:
else
echo "$datetime"$'\t'"$IN" | tee -a logfile
fi
}
function copy () {
command cp -L --parents $@
}
...
copy -R /etc . 2>&1 | log
...
Проблема: При cp -L --parents -R /etc 2>&1
ручном запуске я получаю около 10 сбоев (неработающие символические ссылки, как и ожидалось), а весь /etc копируется.
Но при запуске скрипта сообщается только об 1 сбое, а /etc копируется только в точку, где произошел 1 сбой.
В попытке устранить неполадку я просто удалил 2>&1
скрипт, и копирование продолжилось, как и ожидалось.
Вопрос: Является ли проблема причиной моей log
функции или это какая-то синтаксическая проблема (хотя и не нарушающая скрипт) в способе написания скрипта?
решение1
Ваша log
функция — виновник. Вот что она делает: читает одну строку¹, и если строка не пустая, печатает временную метку, а затем содержимое строки. Это все, что она делает: как только она обработает одну строку, она возвращается.
Когда cp
выдает первое сообщение об ошибке, log
функция считывает его и обрабатывает. Поскольку функция log
затем возвращается, процесс в правой части канала завершается, что приводит к закрытию конца чтения канала. Когдаcp
выдает второе сообщение об ошибке, он пытается записать в закрытый канал, что приводит к его остановке из-заСигнал SIGPIPE. Стандартная ошибка буферизуется построчно (по умолчанию и cp
не пытается это изменить), поэтому буферизация не вступает в игру.
Для обработки всех строк ввода вам понадобится read
цикл.
log () {
while IFS= read -r IN; do
echo "$datetime"$'\t'"$IN"
done | tee -a logfile >&2
}
Я также исправил read
вызовIFS= read -r
для фактического чтения одной строки. Я удалил специальную обработку пустых строк, которая бессмысленна (пустых строк во входных данных не будет). Я предполагаю, что вы вставили ее для обработки случая, когда входные данные пусты (ноль строк ввода), но правильный способ обработки — проверить статус возврата команды read
. Я также исправил log
вывод на ее стандартную ошибку, поскольку она используется для обработки сообщений об ошибках.
ВидетьДобавление временной метки к каждой строке вывода командыдля других способов сделать это.
Помните, что размещение команды в левой части строки имеет существенный недостаток:это приводит к игнорированию статуса выхода. Так что если cp
произойдет сбой, ваш скрипт продолжит работу. Ошибки будут где-то записаны, но последующие команды будут выполняться нормально, и ничто не напомнит вам о том, что вам следует пойти и прочитать логи. В bash, ksh или zsh вы можете установитьpipefail
вариантв дополнение к set -e
этому, ваш скрипт завершается со статусом ошибки, как только любая команда завершается сбоем, даже на левой стороне конвейера.
set -o errexit -o pipefail
copy … |& log
В качестве альтернативы используйтезамена процессавместо конвейера для передачи вывода ошибок через другой процесс. Подстановка процессов имеет несколько иные оговорки, чем конвейеры; ошибки в log
фактически игнорируются, и команда может вернуться до log
завершения (в bash log
выполняется в фоновом режиме).
set -e
copy … 2> >(log)
¹ Почти наверняка вам вообще IFS= read -r IN
не придется читать дальше и, по возможности, не перепутать строчку.