Как определяется статус возврата присвоения переменной?

10

Я видел конструкции в сценариях, таких как это:

if somevar="$(somecommand 2>/dev/null)"; then
...
fi

Это где-то задокументировано? Как определяется возвращаемый статус переменной и как она связана с подстановкой команд? (Например, получу ли я такой же результат с if echo "$(somecommand 2>/dev/null)"; then?)

Wildcard
источник

Ответы:

12

Это задокументировано (для 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.cmd4E=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)cmd1cmd3
    cmd2

Но вот сюрприз:

В моих версиях Bash,

только для чтения
C = что-то A = что-то T = что-то  cmd 0

действительно выполняется. В частности,cmd0

C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 )    cmd 0
выполняет и , но не . (Обратите внимание, что это противоположно его поведению, когда нет команды.) И он устанавливает (а также ) в среде . Интересно, это ошибка в bash?cmd1cmd3cmd2TCcmd0


Не все так просто

Первый абзац этого ответа относится к «простым командам».  В спецификации сказано:

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

Это утверждения, подобные тем, что приведены в моем первом примере блока:

$ 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)
G-Man говорит: «Восстанови Монику»
источник
1
Отлично, спасибо! Знаете ли вы, как получить бонусные баллы local? Например local foo=$(bar)?
Уайлдкарт
1
Для второго примера (просто FOO=$(bar)) стоит отметить, что теоретически и статус выхода, и назначение могут играть роль, см. Unix.stackexchange.com/a/341013/117599
phk
1
@Wildcard: я рад, что тебе нравится. Я только что обновил это снова; большая часть версии, которую вы только что прочитали, была неправильной. Пока ты здесь, что ты думаешь? Это ошибка в bash, которая A=foo cmdзапускается, cmdдаже если Aона доступна только для чтения?
G-Man говорит: «Восстановите Монику»
1
@phk: (1) Интересная теория, но я не уверен, как она имеет смысл. Посмотрите еще раз на пример перед моим «сюрпризом». Если только для Aчтения, команда C=value₁ A=value₂ T=value₃устанавливает, Cно не T(и, конечно, Aне устанавливается) - оболочка прекращает обработку командной строки, игнорируя T=value₃, потому что A=value₂это ошибка. (2) Спасибо за ссылку на вопрос переполнения стека - я разместил коментарии.
G-Man говорит: «Восстановите Монику»
1
@Wildcard «Для бонусных баллов, вы знаете, как местные связи в этом?». Да ... local=$(false)имеет значение выхода , 0потому что (со страницы человека): It is an error to use local when not within a function. The return status is 0 unless local is used outside a function, an invalid name is supplied, or name is a readonly variable.. Этого не достаточно в мире тролля для гения, который спроектировал его таким образом.
Дэвид Тонхофер
2

Это задокументировано в Bash ( LESS=+/'^SIMPLE COMMAND EXPANSION' bash):

Если после расширения осталось имя команды ... В противном случае команда завершается. ... Если не было подстановок команд, команда завершается со статусом ноль.

Другими словами (мои слова):

Если после раскрытия не осталось имени команды и не было выполнено подстановок команд, командная строка завершается со статусом ноль.

PHK
источник