Подтвердить что ты не робот

Код выхода для назначения переменной для подстановки команд в Bash

Я смущен тем, какой код ошибки возвращает команда при выполнении задания переменной и с заменой команды:

a=$(false); echo $?

Он выводит 1, что позволяет мне думать, что присвоение переменной не подметает или не создает новый код ошибки на последнем. Но когда я пробовал это:

false; a=""; echo $?

Он выводит 0, очевидно, это то, что возвращает a="", и переопределяет 1, возвращаемый false.

Я хочу знать, почему это происходит, есть ли какая-то особенность в присваивании переменных, которая отличается от других обычных команд? Или просто быть причиной a=$(false) считается одной командой и имеет смысл только часть замещения команды?

- ОБНОВЛЕНИЕ -

Спасибо всем, из ответов и комментариев я получил точку "Когда вы назначаете переменную с помощью подстановки команд, статус выхода - это статус команды". (by @Barmar), это объяснение превосходно понятно и легко понять, но говорить недостаточно точно для программистов, я хочу видеть ссылку на этот вопрос у таких органов, как TLDP или справочная страница GNU, пожалуйста, помогите мне найти ее, снова спасибо!

4b9b3361

Ответ 1

После выполнения команды $(command) позволяет вывод команды заменить себя.

Когда вы говорите:

a=$(false)             # false fails; the output of false is stored in the variable a

вывод, созданный командой false, сохраняется в переменной a. Кроме того, код выхода такой же, как и команда. help false сказал бы:

false: false
    Return an unsuccessful result.

    Exit Status:
    Always fails.

С другой стороны, говоря:

$ false                # Exit code: 1
$ a=""                 # Exit code: 0
$ echo $?              # Prints 0

приводит к возврату кода возврата для присваивания a, который равен 0.


ИЗМЕНИТЬ:

Цитата из manual:

Если одно из расширений содержало подстановку команд, выход статус команды - это статус выхода последней команды выполняемая замена.

Цитата из BASHFAQ/002:

Как сохранить возвращаемое значение и/или вывод команды в переменная?

...

output=$(command)

status=$?

Назначение output не влияет на статус выхода command, который все еще находится в $?.

Ответ 2

Обратите внимание, что это не тот случай, когда в сочетании с local, как в local variable="$(command)". Эта форма успешно завершится, даже если command не удалось.

Возьмите этот сценарий Bash, например:

#!/bin/bash

function funWithLocalAndAssignmentTogether() {
    local output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

function funWithLocalAndAssignmentSeparate() {
    local output
    output="$(echo "Doing some stuff.";exit 1)"
    local exitCode=$?
    echo "output: $output"
    echo "exitCode: $exitCode"
}

funWithLocalAndAssignmentTogether
funWithLocalAndAssignmentSeparate

Вот результат этого:

[email protected]:~$ ./tmp.sh 
output: Doing some stuff.
exitCode: 0
output: Doing some stuff.
exitCode: 1

Это потому, что local на самом деле является встроенной командой, и такая команда, как local variable="$(command)", вызывает local после подстановки вывода command. Таким образом, вы получаете статус выхода из local.

Ответ 3

Я столкнулся с той же проблемой вчера (29 августа 2018 года).

В дополнение к local, упомянутому в ответе Ника П. и комментарии @sevko в принятом ответе, declare в глобальной области видимости также ведет себя так же.

Вот мой код Bash:

#!/bin/bash

func1()
{
    ls file_not_existed
    local local_ret1=$?
    echo "local_ret1=$local_ret1"

    local local_var2=$(ls file_not_existed)
    local local_ret2=$?
    echo "local_ret2=$local_ret2"

    local local_var3
    local_var3=$(ls file_not_existed)
    local local_ret3=$?
    echo "local_ret3=$local_ret3"
}

func1

ls file_not_existed
global_ret1=$?
echo "global_ret1=$global_ret1"

declare global_var2=$(ls file_not_existed)
global_ret2=$?
echo "global_ret2=$global_ret2"

declare global_var3
global_var3=$(ls file_not_existed)
global_ret3=$?
echo "global_ret3=$global_ret3"

Вывод:

$ ./declare_local_command_substitution.sh 2>/dev/null 
local_ret1=2
local_ret2=0
local_ret3=2
global_ret1=2
global_ret2=0
global_ret3=2

Обратите внимание на значения local_ret2 и global_ret2 в выводе выше. Коды выхода перезаписываются local и declare.

Моя версия Bash:

$ echo $BASH_VERSION 
4.4.19(1)-release

Ответ 4

(не ответ на оригинальный вопрос, но слишком длинный для комментариев)

Обратите внимание, что export A=$(false); echo $? выводит 0! Очевидно, правила, указанные в devnull answer, больше не применяются. Чтобы добавить немного контекста к этой цитате (выделение мое):

3.7.1 Простое расширение команд

...

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

3.7.2 Command Search и Execution [ — this is the "below" case]

Руководство IIUC описывает var=foo как особый случай синтаксиса var=foo command... (довольно запутанно!). Правило "Состояние выхода последней подстановки команд" применяется только в случае отсутствия команд.

Хотя заманчиво думать о export var=foo как о "модифицированном синтаксисе присваивания", это не так - export - встроенная команда (которая, как правило, принимает аргументы, подобные присвоению).

=> Если вы хотите экспортировать состояние подстановки команды var AND, сделайте это в 2 этапа:

A=$(false)
# ... check $?
export A

Этот способ также работает в режиме set -e - немедленно завершается, если подстановка команд возвращает не 0.