Чтобы конвейер zsh сообщал об ошибке на любом этапе, включите опцию PIPE_FAIL перед его запуском. В Терминале на Mac введите setopt PIPE_FAIL; в скрипте поместите эту строку перед конвейером. В этом случае конвейер возвращает код завершения крайней справа завершившейся с ошибкой команды или ноль, если все команды выполнены успешно. Сохраните $? сразу после конвейера, если последующие команды не должны перезаписать этот результат. Это поведение описано в руководстве по опциям zsh.
Почему завершившийся ошибкой конвейер может выглядеть успешным
По умолчанию zsh присваивает конвейеру код завершения его последней команды. Поэтому, если одна из предыдущих команд завершилась с ошибкой, но последняя команда выполнена успешно, весь конвейер может показаться успешным. Например, приведенная ниже команда cat завершится успешно, даже если grep не найдет подходящей строки:
printf '%s\n' 'ready' | grep 'missing' | cat
print -r -- $?
В руководстве по zsh конвейер определяется как последовательность команд, соединенных символом |, и указывается, что его значением обычно является значение последней команды. Нулевой статус означает успех; ненулевой статус означает, что команда или конвейер сообщили об ошибке. Правило по умолчанию описано в документации по конвейерам zsh.
Включение PIPE_FAIL для конвейера
В сессии Терминала задайте опцию перед выполнением команды:
setopt PIPE_FAIL
printf '%s\n' 'ready' | grep 'missing' | cat
print -r -- $?
Теперь итоговый статус будет ненулевым, поскольку команда grep завершилась с ошибкой, даже несмотря на то, что cat выполнилась успешно. PIPE_FAIL использует крайний справа ненулевой статус в конвейере. Это позволяет зафиксировать факт ошибки, сохраняя при этом фактический код состояния, определенный zsh. Чтобы отключить это поведение в текущей сессии, выполните unsetopt PIPE_FAIL.
В скрипте поместите эту опцию ближе к началу, после строки шебанга (interpreter line) и перед любым затрагиваемым конвейером:
#!/bin/zsh
setopt PIPE_FAIL
printf '%s\n' 'ready' | grep 'missing' | cat
pipeline_status=$?
if (( pipeline_status != 0 )); then
print -u2 -- "Pipeline failed with status $pipeline_status"
exit "$pipeline_status"
fi
Явное присвоение статуса имеет значение: переменная $? содержит статус последней выполненной команды, поэтому последующий вызов print, команды для работы с переменными или условные конструкции могут изменить ее значение. Немедленное сохранение значения позволяет скрипту вывести его или завершить работу с этим кодом после выполнения других действий. В данном примере вызывающей программе возвращается статус ошибки; это не останавливает выполнение отдельных команд конвейера в момент сбоя на более раннем этапе.
Использование pipestatus, когда нужен результат каждого этапа
Опция PIPE_FAIL дает обобщенный результат конвейера. Если вам также нужно определить, какая именно команда вернула тот или иной код, в zsh предусмотрен специальный массив pipestatus, содержащий статусы каждой команды последнего конвейера. Считывать его необходимо сразу же, так как следующий конвейер перезапишет эти значения. Массив описан в справочнике по параметрам zsh.
printf '%s\n' 'ready' | grep 'missing' | cat
print -r -- "Stage statuses: ${pipestatus[*]}"
В этом примере из трех команд вывод покажет ноль для printf, ненулевое значение для grep и ноль для cat. Точный ненулевой код зависит от команды. Если вам нужны как общий результат, так и результаты отдельных этапов, используйте PIPE_FAIL и сразу сохраняйте общий $?; для диагностики этапов проверяйте pipestatus сразу после выполнения отдельного конвейера или постройте скрипт так, чтобы сначала сохранить массив и вычислить требуемый общий результат на основе этих сохраненных данных. Избегайте вставки других команд или конвейеров между анализируемым конвейером и местом считывания статуса.
Применение настройки в нужной оболочке
Чтобы применить опцию к последующим командам в интерактивной сессии zsh, выполните setopt PIPE_FAIL в этой сессии. Чтобы включать ее для каждой новой сессии zsh, добавьте эту строку в ~/.zshrc, затем откройте новое окно Терминала или примените изменения с помощью команды source ~/.zshrc. В руководстве по Терминалу от Apple zsh указан как оболочка по умолчанию, однако отмечается, что настройки или администраторы могут выбрать другую оболочку. Убедитесь, какая оболочка активна, прежде чем полагаться на специфичный для zsh синтаксис. То, как Терминал выбирает оболочку, описано в руководстве Apple по Терминалу.
Скрипт, начинающийся с #!/bin/zsh, напрямую запрашивает zsh независимо от того, какая интерактивная оболочка настроена для Терминала. Если скрипт запускается другой оболочкой, setopt и pipestatus не являются переносимыми аналогами для собственных средств управления конвейерами этой оболочки. Следите за соответствием интерпретатора и синтаксиса, а также сохраняйте результат конвейера перед выполнением последующих операций.
Источники
16 Options (zsh) — The Z Shell Manual
6 Shell Grammar (zsh) — The Z Shell Manual
15 Parameters (zsh) — The Z Shell Manual
Изменение оболочки по умолчанию в Терминале на Mac — Служба поддержки Apple