Это задокументировано (для POSIX) в Разделе 2.9.1 Простые команды
Базовых спецификаций Open Group. Там есть стена текста; Я обращаю ваше внимание на последний абзац:
Если есть имя команды, выполнение должно продолжаться, как описано в разделе Поиск и выполнение команд . Если имя команды отсутствует, но команда содержит подстановку команд, команда должна завершиться с состоянием выхода последней выполненной подстановки команд. В противном случае команда должна завершиться с нулевым статусом выхода.
Так, например,
Command Exit Status
$ FOO=BAR 0 (but see also the note from icarus, below)
$ FOO=$(bar) Exit status from "bar"
$ FOO=$(bar) baz Exit status from "baz"
$ foo $(bar) Exit status from "foo"
Так работает bash. Но посмотрите также раздел «не так просто» в конце.
phk , в его вопросе Назначения похожи на команды со статусом выхода, кроме случаев, когда есть подстановка команд? предлагает
… Кажется, что само присвоение считается командой… с нулевым значением выхода, но которое применяется перед правой стороной назначения (например, вызов подстановки команды…)
Это не ужасный взгляд на это. Неочищенная схема для определения статуса возвращаемого простой команды (не содержащая ;
, &
, |
, &&
или ||
) является:
- Сканируйте строку слева направо, пока не дойдете до конца или командного слова (обычно это имя программы).
- Если вы видите присвоение переменной, статус возврата для строки может быть просто 0.
- Если вы видите подстановку команды - то есть
$(…)
- получите статус выхода из этой команды.
- Если вы достигли фактической команды (не в подстановке команд), примите статус выхода из этой команды.
Статус возврата для строки - это последний номер, с которым вы столкнулись.
Подстановки команд в качестве аргументов команды, например, foo $(bar)
не учитываются; Вы получаете статус выхода из foo
. Перефразируя нотацию phk , поведение здесь
temporary_variable = EXECUTE( "bar" )
overall_exit_status = EXECUTE( "foo", temporary_variable )
Но это небольшое упрощение. Общий статус возврата от
A = $ ( cmd 1 ) B = $ ( cmd 2 ) C = $ ( cmd 3 ) D = $ ( cmd 4 ) E = mc 2
это статус выхода из . Задание , которое происходит после того , как задание не устанавливает статус выхода в целом 0.
cmd4
E=
D=
icarus в своем ответе на вопрос phk поднимает важный вопрос: переменные могут быть установлены только для чтения. Третий и последний абзац в разделе 2.9.1 стандарта POSIX гласит:
Если какое-либо из назначений переменной пытается присвоить значение переменной, для которой атрибут readonly установлен в текущей среде оболочки (независимо от того, выполняется ли назначение в этой среде), должна произойти ошибка назначения переменной. См. Последствия ошибок оболочки для последствий этих ошибок.
так что если вы скажете
readonly A
C=Garfield A=Felix T=Tigger
возвращается статус 1. Не имеет значения , если строки Garfield
, Felix
и / или Tigger
заменены командой замещения (S) - но видеть примечания ниже.
Раздел 2.8.1 Последствия ошибок оболочки содержит еще одну группу текста и таблицу, которая заканчивается на
Во всех случаях, показанных в таблице, где от интерактивной оболочки требуется не выходить, оболочка не должна выполнять дальнейшую обработку команды, в которой произошла ошибка.
Некоторые детали имеют смысл; некоторые не делают:
A=
Задание иногда прерывает командную строку, как это последнее предложение кажется уточнить. В приведенном выше примере C
установлено значение Garfield
, но T
не установлено (и, конечно же, ни то, ни другое A
).
- Точно так же
выполняется,
но нет . Но, в моих версиях Баша (которые включают в 4.1.x и 4.3.x), она делает выполнение . (Между прочим, это еще больше ухудшает интерпретацию phk того, что выходное значение назначения применяется перед правой стороной назначения.)
C=$(cmd1) A=$(cmd2) T=$(cmd3)
cmd1
cmd3
cmd2
Но вот сюрприз:
В моих версиях Bash,
только для чтения
C = что-то A = что-то T = что-то cmd 0
действительно выполняется. В частности,cmd0
C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 ) cmd 0
выполняет
и , но не . (Обратите внимание, что это противоположно его поведению, когда нет команды.) И он устанавливает (а также ) в среде . Интересно, это ошибка в bash?
cmd1
cmd3
cmd2
T
C
cmd0
Не все так просто
Первый абзац этого ответа относится к «простым командам».
В спецификации сказано:
«Простая команда» - это последовательность необязательных присвоений и перенаправлений переменных, в любой последовательности, за которыми могут следовать слова и перенаправления, оканчивающиеся оператором управления.
Это утверждения, подобные тем, что приведены в моем первом примере блока:
$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)
первые три из которых включают в себя назначения переменных, а последние три из которых включают подстановки команд.
Но некоторые назначения переменных не так просты.
Баш (1) говорит,
Операторы присваивания могут также появляться в качестве аргументов alias
, declare
, typeset
, export
, readonly
, и local
встроенных команд ( декларации команд).
Для export
, спецификации POSIX говорит,
СТАТУС ВЫХОДА
0Все операнды имен были успешно экспортированы.
> 0Не удалось экспортировать хотя бы одно имя или -p
была указана опция и произошла ошибка.
И POSIX не поддерживает local
, но bash (1) говорит:
Это ошибка для использования, local
когда не в функции. Статус возврата равен 0, если только он local
не используется вне функции, указано неверное имя или имя является переменной только для чтения.
Читая между строк, мы можем видеть, что команды объявления, такие как
export FOO=$(bar)
и
local FOO=$(bar)
больше похоже
foo $(bar)
поскольку они игнорируют статус выхода из bar
и дать вам статус выхода на основе главной команды ( export
, local
или foo
). Итак, у нас есть странность, как
Command Exit Status
$ FOO=$(bar) Exit status from "bar"
(unless FOO is readonly)
$ export FOO=$(bar) 0 (unless FOO is readonly,
or other error from “export”)
$ local FOO=$(bar) 0 (unless FOO is readonly,
statement is not in a function,
or other error from “local”)
что мы можем продемонстрировать с
$ export FRIDAY=$(date -d tomorrow)
$ echo "FRIDAY = $FRIDAY, status = $?"
FRIDAY = Fri, May 04, 2018 8:58:30 PM, status = 0
$ export SATURDAY=$(date -d "day after tomorrow")
date: invalid date ‘day after tomorrow’
$ echo "SATURDAY = $SATURDAY, status = $?"
SATURDAY = , status = 0
и
myfunc() {
local x=$(echo "Foo"; true); echo "x = $x -> $?"
local y=$(echo "Bar"; false); echo "y = $y -> $?"
echo -n "BUT! "
local z; z=$(echo "Baz"; false); echo "z = $z -> $?"
}
$ myfunc
x = Foo -> 0
y = Bar -> 0
BUT! z = Baz -> 1
К счастью, ShellCheck ловит ошибку и поднимает SC2155 , который сообщает, что
export foo="$(mycmd)"
следует изменить на
foo=$(mycmd)
export foo
и
local foo="$(mycmd)"
следует изменить на
local foo
foo=$(mycmd)
local
? Напримерlocal foo=$(bar)
?