Получить коды выхода для канала, когда выход назначен переменной (Command Substitution)

Получение кода выхода команды pipe работает нормально.

echo "ABC" | false | true
echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}
#Output is 0 1 0

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

TEST=$(echo "ABC" | false | true)
echo ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]}
#Output is 0

Как я могу получить коды выхода процессов с каналами?

Ответ 1

Bash замена подстановки происходит в под-оболочке, поэтому эти значения существуют, но только внутри этой под-оболочки. Вы видите ${PIPESTATUS[0]} отражают $? от назначения (это будет 1, если вы закончите замену на | false).

Я изменил ваш пример, чтобы фактически включить некоторый вывод. Это также будет работать с исходным кодом.

# without command substitution
echo "ABC" | false | echo "DEF"
echo "${PIPESTATUS[*]}"

echo "---"

# within command substitution
TEST="$( echo "ABC" | false | echo "DEF"; printf :%s "${PIPESTATUS[*]}" )"
declare -a PIPESTATUS2=( ${TEST##*:} )  # make array w/ content after final colon
if [[ -n "${TEST%:*}" ]]; then          # if there was original output
  TEST="${TEST%:*}"                     # remove trailing results from $TEST
  TEST="${TEST%$'\n'}"                  # remove trailing \n like plain $(…)
else
  TEST=""                               # no original output -> empty string
fi

echo "$TEST"
echo "${PIPESTATUS2[*]}"

Вывод:

DEF
141 1 0
---
DEF
141 1 0

Код выхода 141 происходит из-за того, что false преждевременно завершил конвейер (SIGPIPE).

В основном это просто добавляет массив sub-shell $PIPESTATUS к сохраненному значению, используя двоеточие в качестве разделителя (любая незначная цифра будет делать, я выбрал одну, которую мне не нужно было бежать), а затем вытащил ее из ответ, заполняя массив $PIPESTATUS2 этими значениями. Удаление окончательного разрыва строки - еще один башизм. Мы могли бы использовать это как разделитель, но тогда это сломается, если исходный вывод не будет прерван прерыванием строки.

Упрощенное решение, если вы просто хотите один из кодов выхода:

TEST=$( echo "ABC" | false | true; exit ${PIPESTATUS[0]} )
echo $?  # 141 from `echo "ABC"

Более сложное POSIX-совместимое решение $PIPESTATUS, когда вы можете использовать некоторую темную магию): найдите "Рассмотрим трубопровод" в §1d знаменитого Программирование Csh с учетом вредоносного, а затем адаптируйте его к вашим требованиям, совместимым с POSIX. Это будет нетривиально, но высокообразованным, если вы такой кодер, который должен избегать bash и реальных языков.