Передача ошибок в мою функцию приводит к игнорированию команды после 1 сбоя

Передача ошибок в мою функцию приводит к игнорированию команды после 1 сбоя

Контекст: У меня есть скрипт 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не придется читать дальше и, по возможности, не перепутать строчку.

Связанный контент