Bash обработал неполный список и вернул успех: где теряется статус <(...)

Скрипт строит список файлов для проверки. Генератор успевает напечатать первую строку и затем завершается с ошибкой, например из-за недоступной папки. Bash проверяет единственный полученный файл и сообщает об успехе. Это особенно неприятно, когда в результате нужно решить, можно ли продолжать выпуск. Ошибка генератора не стала статусом команды, которая прочитала его вывод.

Проследите, чей код возврата видит Bash

В Bash выражение <(команда) запускает команду асинхронно и подставляет путь, по которому другой процесс может прочитать её вывод. Это называется подстановкой процесса. В примере mapfile читает строки в массив. Она получает первую строку и затем видит конец потока. Такой же конец потока был бы и у исправного генератора, который просто вывел одну запись.

set -euo pipefail
produce() {
  printf '%s\n' 'part-a.json'
  printf '%s\n' 'inventory: second directory unavailable' >&2
  return 7
}
mapfile -t files < <(produce)
printf 'mapfile status: %s\n' "$?"
printf 'files read: %s\n' "${#files[@]}"

Запустите фрагмент отдельным скриптом через bash. Ожидается сообщение об ошибке от генератора и два результата: mapfile status: 0 и files read: 1. Нулевой статус принадлежит mapfile. pipefail меняет статус конвейера, составленного через |, а здесь результат генератора идёт через подстановку процесса и перенаправление ввода. Замена mapfile на while read сохранит ту же границу: конец ввода ничего не сообщает о причине завершения генератора.

Сначала подтвердите, что список собран целиком

Для небольшого построчного списка с непустыми записями удобнее получить вывод обычной подстановкой команды $(...). Присваивание с такой подстановкой наследует её код возврата, если в присваивании нет имени команды. Поместите присваивание в условие и обработайте сбой до чтения списка.

set -euo pipefail
produce() {
  printf '%s\n' 'part-a.json'
  if [[ ${DEMO_OK:-0} == 1 ]]; then
    printf '%s\n' 'part-b.json'
  else
    printf '%s\n' 'inventory: second directory unavailable' >&2
    return 7
  fi
}
if listing=$(produce); then
  files=()
  if [[ -n $listing ]]; then
    mapfile -t files <<< "$listing"
  fi
else
  status=$?
  printf 'inventory failed: %s\n' "$status" >&2
  exit "$status"
fi
printf 'files ready: %s\n' "${#files[@]}"
for file in "${files[@]}"; do
  printf 'check %s\n' "$file"
done

Сохраните второй фрагмент как check-inventory.sh. При bash check-inventory.sh скрипт возвращает 7 и не выводит ни одной строки check. При DEMO_OK=1 bash check-inventory.sh он получает два имени и обрабатывает оба. Считайте успехом именно эту пару проверок. Первый вариант демонстрирует, что частичный вывод ещё не означает успешную подготовку, второй показывает, что исправление сохранило нормальный сценарий.

Внутри функции сохраняйте явные проверки команд, если от их успеха зависит полнота списка. В примере генератор сам возвращает 7. В реальном скрипте вызов из условного контекста и поведение errexit внутри подстановки усложняют предположения об автоматической остановке. Например, проверяйте каждую обязательную выгрузку условием или явно возвращайте её статус при сбое.

Подстановка команды убирает переводы строки в конце, а <<< добавляет один. Для формата с непустым именем на каждой строке это сохраняет записи. Проверка [[ -n $listing ]] позволяет отдельно представить пустой список без фиктивного элемента. Если список может быть большим, содержит произвольные имена с переводами строки или записи разделены NUL, сохраняйте вывод генератора во временный файл, проверяйте статус записи и начинайте обработку этого файла только после успеха. Так сохраняются исходные байты и размер списка не ограничивается удобством хранения в shell-переменной.

Откройте программу курса DevOps-инженера. В модуле автоматизации вы отработаете коды возврата и строгий режим Bash, затем примените скрипты в учебном стенде с GitLab CI/CD. В пробном уроке о фоновых процессах можно заранее разобрать запуск и наблюдение за задачей на Linux.

Продолжить на практике

  • DevOps-инженер с нуля: полный курс ↗

    Практический курс по DevOps для начинающих: от настройки Linux и сетей до сборки контейнеров, автоматизации в GitLab CI/CD, развёртывания в Kubernetes, мониторинга и восстановления данных на собственном компьютере.