Bash if [false]; возвращает истину

151

На этой неделе изучал bash и столкнулся с загадкой.

#!/bin/sh

if [ false ]; then
    echo "True"
else
    echo "False"
fi

Это всегда будет выводить True, хотя условие может показывать иначе. Если я снимаю скобки, []то это работает, но я не понимаю, почему.

tenmiles
источник
3
Вот кое-что, чтобы вы начали.
Кейсер
10
Кстати, скрипт, начинающийся с #!/bin/shне bash-скрипта, это POSIX sh-скрипт. Даже если интерпретатор POSIX sh в вашей системе предоставляется bash, он отключает несколько расширений. Если вы хотите писать сценарии bash, используйте #!/bin/bashили его локально соответствующий эквивалент ( #!/usr/bin/env bashчтобы использовать первый интерпретатор bash в PATH).
Чарльз Даффи
Это [[ false ]]тоже влияет .
CMCDragonkai

Ответы:

192

Вы запускаете команду [(aka test) с аргументом «ложь», а не запускаете команду false. Поскольку "false" - непустая строка, testкоманда всегда завершается успешно. Чтобы фактически запустить команду, отбросьте [команду.

if false; then
   echo "True"
else
   echo "False"
fi
chepner
источник
4
Мне кажется странным, что ложь - это команда или даже строка, но я думаю, что это работает. Спасибо.
десять минут
31
bashне имеет логического типа данных, и поэтому нет ключевых слов, представляющих истину и ложь. ifОператор просто проверяет , является ли команда , которую вы дать его успех или неудачу. Команда testпринимает выражение и успешно выполняется, если выражение истинно; непустая строка - это выражение, которое оценивается как true, как и в большинстве других языков программирования.falseэто команда, которая всегда терпит неудачу. (По аналогии, trueэто команда, которая всегда добивается успеха.)
chepner
2
if [[ false ]];тоже возвращает true. Как делает if [[ 0 ]];. И if [[ /usr/bin/false ]];не пропускает блок тоже. Как ложь или 0 могут быть оценены как ненулевые? Черт, это расстраивает. Если это имеет значение, OS X 10.8; Баш 3.
Jww
7
Не принимайте falseза булеву константу или 0за целочисленный литерал; оба являются просто обычными строками. [[ x ]]эквивалентно [[ -n x ]]для любой строки x, которая не начинается с дефиса. bashне имеет каких - либо логических констант в любом контексте. Строки, которые выглядят как целые числа, обрабатываются как таковые внутри (( ... ))или с операторами, такими как -eqили -ltвнутри [[ ... ]].
chepner
2
@ tbc0, есть больше проблем под рукой, чем просто как что-то читает. Если переменные устанавливаются кодом, который не полностью находится под вашим контролем (или которым можно манипулировать извне, будь то с помощью необычных имен файлов или иным образом), if $predicateможно легко злоупотребить для выполнения враждебного кода способом, который if [[ $predicate ]]не может.
Чарльз Даффи
55

Быстрый булев праймер для Bash

ifОператор принимает команду в качестве аргумента (как &&,|| и т.д.). Целочисленный код результата команды интерпретируется как логическое значение (0 / null = true, 1 / else = false).

testОператор принимает операторы и операнды в качестве аргументов и возвращает код результата в том же формате, что и if. Псевдоним testоператора [, который часто используется сif для выполнения более сложных сравнений.

Операторы trueи falseничего не делают и возвращают код результата (0 и 1 соответственно). Таким образом, они могут быть использованы как логические литералы в Bash. Но если вы поместите операторы в место, где они интерпретируются как строки, вы столкнетесь с проблемами. В твоем случае:

if [ foo ]; then ... # "if the string 'foo' is non-empty, return true"
if foo; then ...     # "if the command foo succeeds, return true"

Так:

if [ true  ] ; then echo "This text will always appear." ; fi;
if [ false ] ; then echo "This text will always appear." ; fi;
if true      ; then echo "This text will always appear." ; fi;
if false     ; then echo "This text will never appear."  ; fi;

Это похоже на что-то вроде echo '$foo'против echo "$foo".

При использовании testоператора результат зависит от используемых операторов.

if [ "$foo" = "$bar" ]   # true if the string values of $foo and $bar are equal
if [ "$foo" -eq "$bar" ] # true if the integer values of $foo and $bar are equal
if [ -f "$foo" ]         # true if $foo is a file that exists (by path)
if [ "$foo" ]            # true if $foo evaluates to a non-empty string
if foo                   # true if foo, as a command/subroutine,
                         # evaluates to true/success (returns 0 or null)

Короче говоря , если вы просто хотите протестировать что-то как pass / fail (иначе «true» / «false»), то передайте команду в свой оператор ifили &&т. Д. Без скобок. Для сложных сравнений используйте скобки с соответствующими операторами.

И да, я знаю, что в Bash нет такого понятия, как родной логический тип, и что ifи, [и trueс технической точки зрения это «команды», а не «операторы»; это просто очень простое, функциональное объяснение.

Beejor
источник
1
К вашему сведению, если вы хотите использовать 1/0 как True / False в вашем коде, это if (( $x )); then echo "Hello"; fiпоказывает сообщение для, x=1но не для x=0или x=(не определено).
Джонатан Х
3

Я обнаружил, что могу сделать некоторую базовую логику, запустив что-то вроде:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C
Родриго
источник
6
Один может , но это действительно плохая идея. ( $A && $B )это удивительно неэффективная операция - вы порождаете подпроцесс с помощью вызова fork(), затем разбиваете строку содержимого, $Aчтобы создать список элементов (в данном случае размер один), и оценивает каждый элемент как глобус (в данном случае один который оценивает себя), затем запускает результат как команду (в данном случае встроенную команду).
Чарльз Даффи
3
Более того, если ваши trueи falseстроки приходят откуда - то еще, есть риск , что они могут фактически содержать иное , чем содержание trueили false, и вы можете получить неожиданное поведение вплоть до произвольного выполнения команды.
Чарльз Даффи
6
Намного, намного безопаснее писать A=1; B=1и if (( A && B ))- обрабатывать их как числовые - или [[ $A && $B ]], которые рассматривают пустую строку как false и непустую строку как true.
Чарльз Даффи
3
(обратите внимание, что я использую только имена переменных all-caps выше, чтобы следовать соглашениям, установленным в ответе - POSIX фактически явно указывает имена all-caps, которые будут использоваться для переменных среды и встроенных функций оболочки, и резервирует имена в нижнем регистре для использования приложения; чтобы избежать конфликтов с будущими средами ОС, особенно учитывая, что установка переменной оболочки перезаписывает любую переменную среды с таким же именем, всегда идеально соблюдать это соглашение в своих собственных сценариях).
Чарльз Даффи
Спасибо за идеи!
Родриго
2

Использование true / false удаляет некоторые помехи в скобках ...

#! /bin/bash    
#  true_or_false.bash

[ "$(basename $0)" == "bash" ] && sourced=true || sourced=false

$sourced && echo "SOURCED"
$sourced || echo "CALLED"

# Just an alternate way:
! $sourced  &&  echo "CALLED " ||  echo "SOURCED"

$sourced && return || exit
рзп
источник
Я нашел это полезным, но в Ubuntu 18.04 $ 0 был "-bash", и команда basename вызвала ошибку. [[ "$0" =~ "bash" ]] Я изменил этот тест, чтобы скрипт работал на меня.
WiringHarness