PIPESTATUS из команд в $(…|…)

Вопрос или проблема

Как прочитать ${PIPESTATUS[0]} команды, когда переменная установлена в текущей оболочке? Есть ли способ передать ее в текущую оболочку каким-то образом?

Я устанавливаю переменную MAIL=$(ldapsearch.... | find_for_mail) и хочу быть уверенным, что ldapsearch не завершился с ошибкой, прежде чем я решу, что атрибут mail действительно не существует в LDAP.

$ true | false
$ echo ${PIPESTATUS[0]} ${PIPESTATUS[1]}
0 1
$ Z=$(true | false)
$ echo ${PIPESTATUS[0]}
1                        <--- почему 1?
$ echo ${PIPESTATUS[1]}

$ Z=$(true | false;echo ${PIPESTATUS[0]} ${PIPESTATUS[1]})
$ echo $Z
0 1
$ Z=$(true | false;C="${PIPESTATUS[0]} ${PIPESTATUS[1]}")
$ echo $C

$ Z=$(true | false;export C="${PIPESTATUS[0]} ${PIPESTATUS[1]}")
$ echo $C

Проблема в том, что export экспортирует переменные как переменные окружения от родительского процесса к дочернему, а не наоборот. Если вас интересует только один из кодов завершения, вы можете использовать следующее:

$ Z=$(false | true; exit ${PIPESTATUS[0]})
$ echo $?
1

Один из способов сделать это в Linux – передавать данные с помощью временного файла (<<< использует временный файл).

{
  read -u3
  output=$(cmd1 | cmd2 | cmd3; printf '%s\n' "${PIPESTATUS[@]}" > /dev/fd/3)
  readarray -t cmdsubst_pipe_status <&3
} 3<<< ''
printf '%s\n' "статус cmd1: ${cmdsubst_pipe_status[0]}"

Редактирование 2024 года. Такой трюк перестал работать в bash 5.0, где bash обновил разрешения временного файла, используемого для heredoc/herestrings, так что он стал недоступен для записи, что можно было исправить, восстановив их с помощью chmod u+w /dev/fd/3. В bash 5.1 временные файлы были заменены на каналы для коротких heredoc/herestrings. Таким образом, вышеуказанное снова будет работать (хотя мне пришлось добавить read -u3, чтобы игнорировать начальную пустую строку) на Linux (где /dev/fd/x в канале ведет себя как именованный канал), пока printf '%s\n' "${PIPESTATUS[@]}" помещается в буфер канала (по умолчанию 64KiB в Linux, что должно быть вполне достаточно).

Ответ или решение

Для того чтобы правильно обработать результат выполнения команд в конвейере и получить статус их завершения, необходимо учесть, что переменные окружения не передаются из дочерних процессов обратно в родительский. Это ограничение оставляет нас в ситуации, когда мы не можем просто использовать ${PIPESTATUS[0]} после выполнения команды в конструкции $().

Вот несколько способов решения вашей задачи:

  1. Использование временного файла или канала: Как упоминалось в вашем вопросе, можно использовать временный файл или канал, чтобы передать статус завершения команд из дочернего процесса в родительский.

Пример с использованием канала:

{
  read -u3
  output=$(ldapsearch ... | find_for_mail; printf '%s\n' "${PIPESTATUS[@]}" >&3)
  readarray -t cmdsubst_pipe_status <&3
} 3<<< ''
printf 'Статус ldapsearch: %s\n' "${cmdsubst_pipe_status[0]}"

В этом примере мы используем файловый дескриптор 3 для того, чтобы записать статусы завершения на отдельный канал, а затем читаем их обратно в массив cmdsubst_pipe_status.

  1. Прямое чтение статуса после выполнения команд в конвейере: Если нужно просто получить статус первой команды в конвейере, можно обернуть команды в фигурные скобки и использовать exit ${PIPESTATUS[0]}:
Z=$(ldapsearch ... | find_for_mail; exit ${PIPESTATUS[0]})
if [ $? -eq 0 ]; then
  echo "ldapsearch выполнен успешно."
else
  echo "Ошибка в ldapsearch."
fi

Тем не менее, так как в некоторых ситуациях вы можете не захотеть прерывать выполнение скрипта, рассматриваете вариант с временным файлом или аналогом.

  1. Использование комбинации trap и временного файла: Также удобно использовать команду trap, чтобы передать ошибки:
temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT
{
  ldapsearch ... | find_for_mail; echo ${PIPESTATUS[0]} > "$temp_file"
}
wait $!
status="$(<"$temp_file")"
printf 'Статус ldapsearch: %s\n' "$status"

Эти методы обеспечат передачу статусов выполнения команд из конвейера и нивелируют ограничения, связанные с зависимостями между родительскими и дочерними процессами.

Учитывая возможности вашего окружения, выберите наиболее подходящий метод для сбора статусов выполнения и обработки ошибок в скриптах.

Оцените материал
Добавить комментарий

Капча загружается...