Як визначається стан повернення змінної призначення?


10

Я бачив конструкції в таких сценаріях:

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

Це десь задокументовано? Як визначається статус повернення змінної та як вона стосується підстановки команд? (Наприклад, чи отримав би я такий самий результат if echo "$(somecommand 2>/dev/null)"; then?)

Відповіді:


13

Це задокументовано (для POSIX) у Розділі 2.9.1 Прості команди Технічні характеристики бази відкритої групи. Там стіна тексту; Я звертаю вашу увагу на останній абзац:

Якщо є ім'я команди, виконання продовжуватиметься так, як описано в пошуку та виконанні команд . Якщо імені команди немає, але команда містила підстановку команди, команда повинна завершуватись статусом виходу останньої заміни команди, виконаної. В іншому випадку команда повинна завершитися нульовим статусом виходу.

Так, наприклад,

   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"

Так працює і баш. Але дивіться також "не так простий" розділ наприкінці.

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=

Ікар , в своїй відповіді на питання ФГК в , піднімає важливе питання: змінні можуть бути встановлені як незмінні. Абзац третій до останнього в Розділі 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).
  • Аналогічно виконує, але ні . Але в моїх версіях bash (які включають 4.1.X і 4.3.X), він дійсно виконується . (Між іншим, це додатково перешкоджає тлумаченню phk про те, що значення виходу завдання призначається перед правою частиною завдання.)C=$(cmd1) A=$(cmd2) T=$(cmd3)cmd1cmd3
    cmd2

Але ось сюрприз:

У моїх версіях bash,

тільки читати A
C = щось A = щось T = щось  cmd 0

це виконати. Зокрема,cmd0

C = $ ( cmd 1 ) A = $ ( cmd 2 ) T = $ ( cmd 3 )    cmd 0
виконує і , але ні . (Зауважте, що це протилежне його поведінці, коли немає команди.) І він встановлює (як і ) в середовищі . Цікаво, чи це помилка в баші.cmd1cmd3cmd2TCcmd0


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

Перший абзац цієї відповіді стосується "простих команд".  Специфікація говорить:

"Проста команда" - це послідовність необов'язкових присвоювань змінних та перенаправлень у будь-якій послідовності, необов'язково слідуючи за словами та перенаправленнями, що закінчуються оператором управління.

Це такі твердження, як ті, що в моєму першому прикладі:

$ FOO=BAR
$ FOO=$(bar)
$ FOO=$(bar) baz
$ foo $(bar)

перші три з яких містять змінні призначення, а останні три з яких містять підстановки команд.

Але деякі змінні завдання не такі вже й прості.  bash (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)

1
Чудово, дякую! Чи знаєте ви бонусні бали, як localув'язуєте це? Наприклад local foo=$(bar)?
Wildcard

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):

Якщо після розширення залишилося ім'я команди .... В іншому випадку команда виходить. ... Якщо підстановок команд не було, команда виходить зі статусом нуля.

Іншими словами (мої слова):

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.