Как объявить и использовать логические переменные в сценарии оболочки? - PullRequest
795 голосов
/ 02 июня 2010

Я попытался объявить логическую переменную в сценарии оболочки, используя следующий синтаксис:

variable=$false

variable=$true

Это правильно? Кроме того, если бы я хотел обновить эту переменную, я бы использовал тот же синтаксис? Наконец, следующий синтаксис для использования логических переменных в качестве правильных выражений:

if [ $variable ]

if [ !$variable ]

Ответы [ 17 ]

983 голосов
/ 02 июня 2010

Исправленный ответ (12 февраля 2014 г.)

the_world_is_flat=true
# ...do something interesting...
if [ "$the_world_is_flat" = true ] ; then
    echo 'Be careful not to fall off!'
fi

Оригинальный ответ

Предостережения: https://stackoverflow.com/a/21210966/89391

the_world_is_flat=true
# ...do something interesting...
if $the_world_is_flat ; then
    echo 'Be careful not to fall off!'
fi

From: Использование логических переменных в Bash

Причина, по которой первоначальный ответ включен сюда, состоит в том, что комментарии до пересмотра 12 февраля 2014 года относятся только к первоначальному ответу, и многие комментарии неверны, когда связаны с пересмотренным ответом. Например, комментарий Денниса Уильямсона о bash builtin true от 2 июня 2010 г. относится только к первоначальному ответу, а не к пересмотренному.

696 голосов
/ 19 января 2014

TL; DR

bool=true

if [ "$bool" = true ]

Проблемы с ответом Мику ( оригинал )

Я не рекомендую принятый ответ 1 . У него красивый синтаксис, но есть некоторые недостатки.

Скажем, у нас есть следующее условие.

if $var; then
  echo 'Muahahaha!'
fi

В следующих случаях 2 это условие оценивается как true и выполняет вложенную команду.

# Variable var not defined beforehand. Case 1
var=''  # Equivalent to var="".        Case 2
var=    #                              Case 3
unset var  #                           Case 4
var='<some valid command>'  #          Case 5

Как правило, вы хотите, чтобы ваше условие оценивалось как true, когда ваша «логическая» переменная, var в этом примере, явно установлена ​​в true. Все остальные дела опасно вводят в заблуждение!

Последний случай (# 5) особенно капризен, потому что он выполнит команду, содержащуюся в переменной (поэтому условие оценивается как истинное для допустимых команд 3, 4 ).

Вот безобидный пример:

var='echo this text will be displayed when the condition is evaluated'
if $var; then
  echo 'Muahahaha!'
fi

# Outputs:
# this text will be displayed when the condition is evaluated
# Muahahaha!

Цитировать ваши переменные безопаснее, например, if "$var"; then. В вышеуказанных случаях вы должны получить предупреждение о том, что команда не найдена. Но мы все еще можем сделать лучше (см. Мои рекомендации внизу).

Также см. Объяснение Майка Холта об исходном ответе Мику.

Проблемы с ответом Хбара

Этот подход также имеет неожиданное поведение.

var=false
if [ $var ]; then
  echo "This won't print, var is false!"
fi

# Outputs:
# This won't print, var is false!

Вы могли бы ожидать, что вышеприведенное условие оценивается как ложное, поэтому никогда не выполняете вложенное выражение. Сюрприз!

Цитируя значение ("false"), цитируя переменную ("$var") или используя test или [[ вместо [, ничего не измените.

Что я рекомендую:

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

bool=true

if [ "$bool" = true ]; then
if [ "$bool" = "true" ]; then

if [[ "$bool" = true ]]; then
if [[ "$bool" = "true" ]]; then
if [[ "$bool" == true ]]; then
if [[ "$bool" == "true" ]]; then

if test "$bool" = true; then
if test "$bool" = "true"; then

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


Сноска

  1. Ответ Мику с тех пор был отредактирован и больше не содержит (известных) недостатков.
  2. Не исчерпывающий список.
  3. Допустимая команда в этом контексте означает существующую команду. Неважно, если команда используется правильно или неправильно. Например. man woman все равно будет считаться допустимой командой, даже если такой справочной страницы не существует.
  4. Для недопустимых (несуществующих) команд Bash просто будет жаловаться, что команда не найдена.
  5. Если вы заботитесь о длине, первая рекомендация самая короткая.
146 голосов
/ 14 февраля 2014

Кажется, здесь есть некоторое недопонимание по поводу встроенного в Bash true, а более конкретно, о том, как Bash расширяет и интерпретирует выражения в скобках.

Код в ответе miku не имеет абсолютно никакого отношения ни к встроенной в Bash true, ни к /bin/true, ни к какой-либо другой разновидности команды true. В этом случае true является не чем иным, как простой строкой символов, и вызов true команды / встроенной команды никогда не выполняется ни с помощью присвоения переменной, ни с помощью вычисления условного выражения.

Следующий код функционально идентичен коду в ответе мику:

the_world_is_flat=yeah
if [ "$the_world_is_flat" = yeah ]; then
    echo 'Be careful not to fall off!'
fi

Разница только в заключается в том, что сравниваемыми являются четыре символа 'y', 'e', ​​'a' и 'h' вместо 't', 'r', 'u' и 'е'. Вот и все. Не было предпринято никаких попыток вызвать команду или встроенную команду с именем yeah, и при этом (в примере miku) не происходит какой-либо специальной обработки, когда Bash анализирует токен true. Это просто строка, причем совершенно произвольная.

Обновление (2014-02-19): После перехода по ссылке в ответе Мику, теперь я вижу, откуда происходит некоторая путаница. В ответе Мику используются одиночные скобки, но фрагмент кода, на который он ссылается, не использует скобки. Это просто:

the_world_is_flat=true
if $the_world_is_flat; then
  echo 'Be careful not to fall off!'
fi

Оба фрагмента кода будут вести себя одинаково, но скобки полностью изменяют то, что происходит под капотом.

Вот что делает Bash в каждом случае:

Без скобок:

  1. Развернуть переменную $the_world_is_flat до строки "true".
  2. Попытка разобрать строку "true" как команду.
  3. Найдите и выполните команду true (встроенную или /bin/true, в зависимости от версии Bash).
  4. Сравните код выхода команды true (который всегда равен 0) с 0. Напомним, что в большинстве оболочек код выхода 0 указывает на успех, а все остальное указывает на сбой.
  5. Поскольку код выхода был 0 (успех), выполните предложение then оператора if

Кронштейны:

  1. Развернуть переменную $the_world_is_flat до строки "true".
  2. Разобрать теперь полностью расширенное условное выражение, имеющее форму string1 = string2. Оператор = является оператором bash сравнения строк . Итак ...
  3. Выполнить сравнение строк для "true" и "true".
  4. Да, две строки были одинаковыми, поэтому значение условного выражения истинно.
  5. Выполнить предложение if оператора then.

Код без скобок работает, потому что команда true возвращает код завершения 0, который указывает на успех. Код в скобках работает, потому что значение $the_world_is_flat идентично строковому литералу true с правой стороны =.

Просто чтобы понять, рассмотрим следующие два фрагмента кода:

Этот код (при запуске с правами суперпользователя) перезагрузит компьютер:

var=reboot
if $var; then
  echo 'Muahahaha! You are going down!'
fi

Этот код просто печатает "Хорошая попытка". Команда перезагрузки не вызывается.

var=reboot
if [ $var ]; then
  echo 'Nice try.'
fi

Обновление (2014-04-14) Чтобы ответить на вопрос в комментариях относительно разницы между = и ==: AFAIK, разницы нет. Оператор == является синонимом Bash для =, и, насколько я видел, они работают одинаково во всех контекстах.

Обратите внимание, что я специально говорю об операторах сравнения строк = и ==, используемых в тестах [ ] или [[ ]]. Я не предполагаю, что = и == взаимозаменяемы везде в bash.

Например, вы явно не можете назначать переменные с помощью ==, например var=="foo" (технически вы можете сделать это, но значение var будет "=foo" поскольку Bash не видит здесь оператора ==, он видит оператор = (присваивание), за которым следует буквальное значение ="foo", которое просто становится "=foo").

Кроме того, хотя = и == являются взаимозаменяемыми, вы должны помнить, что как эти тесты работают зависит от того, используете ли вы его внутри [ ] или [[ ]], а также о том, цитируются ли операнды. Вы можете прочитать больше об этом в Руководство по расширенному написанию сценариев Bash: 7.3 Другие операторы сравнения (прокрутите вниз до обсуждения = и ==).

47 голосов
/ 14 ноября 2014

Используйте арифметические выражения.

#!/bin/bash

false=0
true=1

((false)) && echo false
((true)) && echo true
((!false)) && echo not false
((!true)) && echo not true

Выход:

правда
не ложь

25 голосов
/ 03 ноября 2017

Короче говоря:

В bash нет логических значений

Что bash имеет, так это булевы выражения с точки зрения сравнения и условий. Тем не менее, то, что вы можете объявить и сравнить в bash, это строки и числа. Вот и все.

Везде, где вы видите true или false в bash, это либо строка, либо команда / встроенная функция, которая используется только для кода выхода.

Этот синтаксис ...

if true; then ...

по существу ...

if COMMAND; then ...

Условие истинно, когда команда возвращает код завершения 0. true и false являются встроенными функциями Bash, а иногда и автономными программами, которые ничего не делают, кроме возврата соответствующего кода выхода.

Условие выше эквивалентно:

COMMAND && ...

При использовании квадратных скобок или команды test вы полагаетесь на код выхода этой конструкции. Имейте в виду, что [ ] и [[ ]] также просто команды / встроенные функции, как и любые другие. Итак ...

if [[ 1 == 1 ]]; then echo yes; fi

соответствует

if COMMAND; then echo yes; fi

и COMMAND здесь [[ 1 == 1 ]]

Конструкция if..then..fi является просто синтаксическим сахаром. Вы всегда можете просто запустить команды, разделенные двойным амперсандом, для того же эффекта:

[[ 1 == 1 ]] && echo yes

При использовании true и false в этих конструкциях тестирования вы фактически передаете только команду "true" или "false" команде тестирования. Вот пример:

Верьте или нет, но все эти условия дают одинаковый результат :

if [[ false ]]; then ...
if [[ "false" ]]; then ...
if [[ true ]]; then ...
if [[ "true" ]]; then ...

TL; DR; всегда сравнивайте со строками или числами

Чтобы это было понятно будущим читателям, я бы рекомендовал всегда использовать кавычки вокруг true и false:

DO

if [[ "${var}" == "true" ]]; then ...
if [[ "${var}" == "false" ]]; then ...
if [[ -n "${var:-}" ]]; then echo "var is not empty" ...

НЕ

if [ ... ]; then ...  # always use double square brackets in bash!
if [[ "${var}" ]]; then ...  # this is not as clear or searchable as -n
if [[ "${var}" != true ]]; then ...  # creates impression of booleans
if [[ "${var}" -eq "true" ]]; then ...  # `-eq` is for numbers and doesn't read as easy as `==`

Может

if [[ "${var}" != "true" ]]; then ...  # creates impression of booleans. Can be used for strict checking of dangerous operations. This condition is false for anything but the literal string "true". 
15 голосов
/ 11 апреля 2013

Давным-давно, когда все, что у нас было, это sh, логические значения обрабатывались, полагаясь на соглашение программы test, где test возвращает ложное состояние выхода, если выполняется без аргументов. Это позволяет думать о переменной, которая не установлена ​​как ложная, и переменной, установленной в любое значение, как истинная. Сегодня test встроен в bash и обычно известен своим псевдонимом символа [ (или исполняемым файлом для использования в оболочках, в которых его нет, как отмечает дольмен):

FLAG="up or <set>"

if [ "$FLAG" ] ; then 
    echo 'Is true'
else 
    echo 'Is false'
fi

# unset FLAG
#    also works
FLAG=

if [ "$FLAG" ] ; then
    echo 'Continues true'
else
    echo 'Turned false'
fi

Из-за соглашений о цитировании авторы сценариев предпочитают использовать составную команду [[, которая имитирует test, но имеет более приятный синтаксис: переменные с пробелами не нужно заключать в кавычки, можно использовать && и || в качестве логические операторы со странным приоритетом, и в POSIX нет ограничений на количество терминов.

Например, чтобы определить, установлен ли флаг FLAG и число COUNT больше 1:

FLAG="u p"
COUNT=3

if [[ $FLAG  && $COUNT -gt '1' ]] ; then 
    echo 'Flag up, count bigger than 1'
else 
    echo 'Nope'
fi

Этот материал может запутаться , когда требуются пробелы, строки нулевой длины и нулевые переменные, а также когда ваш сценарий должен работать с несколькими оболочками.

11 голосов
/ 30 апреля 2016

Как объявить и использовать логические переменные в сценарии оболочки?

В отличие от многих других языков программирования, Bash не разделяет свои переменные по «типу». [1]

Таким образом, ответ довольно ясен.В bash нет boolean variable.Однако:

Используя оператор объявления, мы можем ограничить присвоение значения переменными. [2]

#!/bin/bash
declare -ir BOOL=(0 1) #remember BOOL can't be unset till this shell terminate
readonly false=${BOOL[0]}
readonly true=${BOOL[1]}
#same as declare -ir false=0 true=1
((true)) && echo "True"
((false)) && echo "False"
((!true)) && echo "Not True"
((!false)) && echo "Not false"

Параметр r в declare иreadonly используется, чтобы явно указать, что переменные только для чтения .Надеюсь, цель ясна.

9 голосов
/ 08 сентября 2014

Вместо того, чтобы выдавать логическое значение и оставлять ловушку для будущих читателей, почему бы просто не использовать лучшее значение, чем true и false?

Например:

build_state=success
if something-horrible; then
  build_state=failed
fi

if [[ "$build_state" == success ]]; then
  echo go home, you are done
else
  echo your head is on fire, run around in circles
fi
4 голосов
/ 01 февраля 2018

POSIX (интерфейс переносимой операционной системы)

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

По сути, все проголосовавшие ответы верны, за исключением того, что они BASH слишком специфичны.

По сути, я только хочу добавить больше информации о переносимости.


  1. [ и ], такие как [ "${var}" = true ], не нужны, и вы можете их опустить и напрямую использовать команду test:

    test "${var}" = true && CodeIfTrue || CodeIfFalse
    

    Обратите внимание, что я больше не рекомендую это , поскольку оно медленно устарело.

  2. Представьте себе, что эти слова true и false означают для оболочки, проверьте сами:

    echo $(( true ))
    
    0
    
    echo $(( false ))
    
    1
    

    Но используя кавычки:

    echo $(( "true" ))
    
    bash: "true": syntax error: operand expected (error token is ""true"")
    sh (dash): sh: 1: arithmetic expression: expecting primary: ""true""
    

    То же самое касается:

    echo $(( "false" ))
    

    Оболочка не может интерпретировать ее, кроме строки. Надеюсь, вы поняли, насколько хорошо использовать правильное ключевое слово без кавычек.

    Но никто не сказал этого в предыдущих ответах.

  3. Что это значит? Ну, несколько вещей.

    • Вы должны привыкнуть, что булевы ключевые слова на самом деле обрабатываются как числа, то есть true = 0 и false = 1, помните, что все ненулевые значения обрабатываются как false .

    • Поскольку они обрабатываются как числа, вы должны обращаться с ними так же, то есть, если вы определяете переменную, скажите:

      var_bool=true
      echo "${var_bool}"
      
       true
      

      Вы можете создать противоположное значение с помощью:

      var_bool=$(( 1 - ${var_bool} ))
      echo "${var_bool}"
      
      1
      

      Как вы сами видите, оболочка печатает строку true в первый раз, когда вы ее используете, но с тех пор все работает через число 0, представляющее true или 1, представляющее false соответственно.


Наконец, что вы должны делать со всей этой информацией

  • Во-первых, одна хорошая привычка будет назначать 0 вместо true; 1 вместо false.

  • Второй хорошей привычкой будет проверка, если переменная равна или не равна нулю:

    if [ "${var_bool}" -eq 0 ]; then YourCodeIfTrue; else YourCodeIfFalse; fi
    
3 голосов
/ 21 сентября 2016

Биллу Паркеру отказывают , потому что его определения противоположны обычному соглашению о коде. Обычно true определяется как 0, а false определяется как ненулевой. 1 будет работать для ложного, как и 9999 и -1. То же самое с возвращаемыми значениями функции - 0 - успех, а все ненулевое значение - сбой. Извините, у меня пока нет авторитета на улице, чтобы голосовать или отвечать ему напрямую.

Bash рекомендует теперь использовать двойные скобки как привычку вместо одиночных скобок, и ссылка, которую дал Майк Холт, объясняет различия в том, как они работают. 7,3. Другие операторы сравнения

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

#**** NOTE *** This gives error message *****
The_world_is_flat=0;
if [ "${The_world_is_flat}" -eq true ]; then

выдаст сообщение об ошибке, ожидая целочисленное выражение. Это относится к любому параметру, поскольку ни одно из них не является целочисленным значением. Тем не менее, если мы заключим в него двойные скобки, он не выдаст сообщение об ошибке, но приведет к неправильному значению (ну, в 50% возможных перестановок). Он будет иметь значение [[0 -eq true]] = success, но также [[0 -eq false]] = success, что неверно (хммм .... как насчет того, чтобы встроенная функция была числовым значением?). 1015 *

#**** NOTE *** This gives wrong output *****
The_world_is_flat=true;
if [[ "${The_world_is_flat}" -eq true ]]; then

Существуют и другие перестановки условного выражения, которые также дадут неправильный вывод. По сути, все (кроме состояния ошибки, указанного выше), которое устанавливает переменную в числовое значение и сравнивает ее со встроенной функцией true / false, или устанавливает переменную со встроенной функцией true / false и сравнивает ее с числовым значением. Кроме того, все, что устанавливает переменную во встроенное значение true / false и выполняет сравнение, используя -eq. Поэтому избегайте -eq для логических сравнений и избегайте использования числовых значений для логических сравнений. Вот сводка перестановок, которые приведут к неверным результатам:

#With variable set as an integer and evaluating to true/false
#*** This will issue error warning and not run: *****
The_world_is_flat=0;
if [ "${The_world_is_flat}" -eq true ]; then

#With variable set as an integer and evaluating to true/false
#*** These statements will not evaluate properly: *****
The_world_is_flat=0;
if [ "${The_world_is_flat}" -eq true ]; then
#
if [[ "${The_world_is_flat}" -eq true ]]; then
#
if [ "${The_world_is_flat}" = true ]; then
#
if [[ "${The_world_is_flat}" = true ]]; then
#
if [ "${The_world_is_flat}" == true ]; then
#
if [[ "${The_world_is_flat}" == true ]]; then


#With variable set as an true/false builtin and evaluating to true/false
#*** These statements will not evaluate properly: *****
The_world_is_flat=true;
if [[ "${The_world_is_flat}" -eq true ]]; then
#
if [ "${The_world_is_flat}" = 0 ]; then
#
if [[ "${The_world_is_flat}" = 0 ]]; then
#
if [ "${The_world_is_flat}" == 0 ]; then
#
if [[ "${The_world_is_flat}" == 0 ]]; then

Итак, теперь к тому, что работает. Используйте встроенные true / false для сравнения и оценки (как отметил Майк Хант, не заключайте их в кавычки). Затем используйте одинарный или двойной знак равенства (= или ==) и одинарные или двойные скобки ([] или [[]]). Лично мне нравится знак двойного равенства, потому что он напоминает мне о логических сравнениях в других языках программирования, и двойные кавычки только потому, что я люблю печатать. Итак, эти работы:

#With variable set as an integer and evaluating to true/false
#*** These statements will work properly: *****
#
The_world_is_flat=true/false;
if [ "${The_world_is_flat}" = true ]; then
#
if [[ "${The_world_is_flat}" = true ]]; then
#
if [ "${The_world_is_flat}" = true ]; then
#
if [[ "${The_world_is_flat}" == true ]]; then

Вот, пожалуйста.

...