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

Почему `if $(true); затем... fi` успешный?

Вдохновленный этот вопрос:

Что должен делать оператор if, когда условие является заменой команды, когда команда не производит вывод?

ПРИМЕЧАНИЕ: Пример if $(true); then ..., а не if true ; then ...

Например, данный:

if $(true) ; then echo yes ; else echo no ; fi

Я бы подумал, что $(true) следует заменить выходом команды true, которая ничего. Затем он должен быть эквивалентен этому:

if "" ; then echo yes ; else echo no ; fi

который печатает no, потому что нет команды, имя которой является пустой строкой, или к этому:

if ; then echo yes ; else echo no ; fi

который является синтаксической ошибкой.

Но эксперимент показывает, что если команда не производит вывод, оператор if рассматривает его как истинный или ложный в зависимости от состояния команды, а не от ее выхода.

Здесь script, который демонстрирует поведение:

#!/bin/bash

echo -n 'true:          ' ; if true          ; then echo yes ; else echo no ; fi
echo -n 'false:         ' ; if false         ; then echo yes ; else echo no ; fi
echo -n '$(echo true):  ' ; if $(echo true)  ; then echo yes ; else echo no ; fi
echo -n '$(echo false): ' ; if $(echo false) ; then echo yes ; else echo no ; fi
echo -n '$(true):       ' ; if $(true)       ; then echo yes ; else echo no ; fi
echo -n '$(false):      ' ; if $(false)      ; then echo yes ; else echo no ; fi
echo -n '"":            ' ; if ""            ; then echo yes ; else echo no ; fi
echo -n '(nothing):     ' ; if               ; then echo yes ; else echo no ; fi

и здесь вывод я получаю (Ubuntu 11.04, bash 4.2.8):

true:          yes
false:         no
$(echo true):  yes
$(echo false): no
$(true):       yes
$(false):      no
"":            ./foo.bash: line 9: : command not found
no
./foo.bash: line 10: syntax error near unexpected token `;'
./foo.bash: line 10: `echo -n '(nothing):     ' ; if               ; then echo yes ; else echo no ; fi'

Первые четыре строки ведут себя так, как я ожидал; строки $(true) и $(false) удивительны.

Дальнейший эксперимент (не показан здесь) указывает, что если команда между $( и ) выдает вывод, его статус выхода не влияет на поведение if.

Я вижу подобное поведение (в некоторых случаях - разные сообщения об ошибках) с bash, ksh, zsh, ash и dash.

Я ничего не вижу в документации bash или в спецификации языка командной строки POSIX, чтобы объяснить это.

(Или, возможно, мне не хватает чего-то очевидного.)

EDIT: В свете принятого ответа, вот еще один пример поведения:

command='' ; if $command ; then echo yes ; else echo no ; fi

или, что эквивалентно:

command=   ; if $command ; then echo yes ; else echo no ; fi
4b9b3361

Ответ 1

См. раздел 2.9.1 описания языка . Последнее предложение первого раздела гласит:

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

$(true) расширяется до пустой строки. Оболочка анализирует пустую строку и обнаруживает, что команда не указана и следует приведенному выше правилу.

Ответ 2

Есть потенциально два кода выхода для рассмотрения. Во-первых, вот еще два эксперимента, которые должны помочь:

# if $(echo true; false) ; then echo yes ; else echo no ; fi
yes

Внутренняя команда выходит из строя из-за false. Но это не имеет значения, потому что вывод команды не пуст и, следовательно, выполняется вывод ( "true" ), и его код выхода имеет приоритет.

# if $(echo false; true) ; then echo yes ; else echo no ; fi
no

Опять же, командная строка внутри $( ) выполнена успешно, но выход не имеет значения, потому что выход ( "false" ) имеет приоритет.

Статус выхода команд внутри $( ) имеет значение тогда и только тогда, когда вывод пуст или только пробел. Если выход пуст, нет списка команд для выполнения, и, следовательно, появляется, что оболочка вернется к статусу выхода внутренней команды.

Ответ 3

Что происходит, так это то, что bash сохраняет статус выхода последней выполненной команды

Это объясняет, почему $(true) и $(false) имеют различное поведение в тесте if. Они оба генерируют нулевые команды, которые не считаются исполнением, но имеют разные коды выхода.

Как только вы используете подстановку команд для команды, которая имеет выход, $() пытается выполнить этот вывод как команду, а код выхода этой попытки теперь является последним, используемым для теста if

Ответ 4

Что должен делать оператор if, когда условие является заменой команды, когда команда не производит вывод?

Выход не имеет значения. Важно то, что код выхода:

   if list; then list; [ elif list; then list; ] ... [ else
   list; ] fi
          The if list is executed.  If its exit status is zero,
          the then list is executed.  Otherwise, each elif list
          is executed in turn, and if its exit status is zero,
          the corresponding then list is executed and the
          command completes.  Otherwise, the else list is
          executed, if present.  The exit status is the exit
          status of the last command executed, or zero if no
          condition tested true.

Если вы замените $(echo true) и $(echo false) на что-то еще, вы, вероятно, увидите, что происходит:

$ if $(echo false) ; then echo yes ; else echo no ; fi
no
$ if $(echo command-does-not-exist) ; then echo yes ; else echo no ; fi
command-does-not-exist: command not found
no
$ 

$(..) запускает команду, а затем if выполняет результаты (в этом случае просто true или false или does-not-exist). Пустой $() запускает подоболочку, которая успешно выполняется до завершения и возвращает код выхода 0:

$ if $() ; then echo yes ; else echo no ; fi
yes

Аарон поднял некоторые интересные моменты:

$ if $(echo true; false) ; then echo yes ; else echo no ; fi
yes
$ if $(echo 'true ; false') ; then echo yes ; else echo no ; fi
yes
$ if true ; false ; then echo yes ; else echo no ; fi
no

$ if $(echo false ; true) ; then echo yes ; else echo no ; fi
no
$ if $(echo 'false ; true') ; then echo yes ; else echo no ; fi
no
$ if false ; true ; then echo yes ; else echo no ; fi
yes
$ 

Похоже, что при выполнении команды, построенной с помощью $() подоболочки, имеет значение первый статус выхода команды.