Почему использование переменной Bash readonly для захвата вывода не позволяет получить код возврата $?

Вот пример, который пытается выполнить команду и проверяет, был ли он выполнен успешно, при этом он выводится для дальнейшей обработки:

#!/bin/bash

readonly OUTPUT=$(foo)
readonly RES=$?

if [[ ${RES} != 0 ]]
then
    echo "failed to execute foo"
    exit 1
else
    echo "foo success: '${OUTPUT}'"
fi

Сообщается, что это был успех, даже нет такого исполняемого файла foo. Но, если я удаляю переменную readonly из OUTPUT, она сохраняет ошибочный код выхода и обнаруживается сбой.

Я пытаюсь использовать readonly как для техники "защитного программирования", как рекомендовано где-то... но похоже, что в этом случае он кусает себя.

Есть ли какое-то чистое решение для сохранения readonly, сохраняя при этом код выхода команды/подоболочки? Было бы неутешительно, что нужно помнить этот исключительный случай использования или вернуться к использованию readonly когда-либо...

Использование Bash 4.2.37 (1) в Debian Wheezy.

Ответ 1

Проблема заключается в том, что readonly - это собственная команда, а возвращаемый код, который он возвращает, - это собственный код выхода, а не код выхода подстановки команды.

От help readonly:

Статус выхода:
Возвращает успех, если не указана недопустимая опция или NAME недействителен.

Итак, вам нужно использовать две отдельные команды:

$ output=$(false)
$ readonly res=$?
$ readonly output

Это сохраняет код выхода, который вы хотите:

$ echo $res
1

За исключением ввода отладчика, невозможно отключить переменную readonly. Таким образом, не устанавливайте переменную readonly, если вы не хотите, чтобы она оставалась постоянной для оставшейся части сеанса bash.

Изменение

Две команды readonly могут быть объединены в один (tip tip: Chepner):

$ output=$(false)
$ readonly output res=$?
$ echo $res
1

Помимо

Лучше всего использовать имена с меньшим или смешанным регистром для ваших переменных. Система использует все имена верхнего регистра, и вы не хотите случайно перезаписывать системную переменную.