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

506

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

variable=$false

variable=$true

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

if [ $variable ]

if [ !$variable ]
  • 47
    BEWARE! true и false в контексте большинства приведенных ниже фрагментов - просто простые строки, а не bash built-ins !!! Пожалуйста, прочитайте ответ Майка Холта ниже. (Это один из примеров, когда высоко оцененный и принятый ответ ИМХО сбивает с толку и затеняет проницательный контент в менее проголосовавших ответах)
  • 7
    @mjv Большая часть путаницы по поводу этого вопроса (и ответа Мику) была вызвана тем, что Мику пересмотрел свой ответ в какой-то момент после того, как было опубликовано несколько комментариев, описывающих, как ответ Мику включал в себя вызов встроенного в bash значения true . Оказывается, первоначальный ответ Мику действительно называл true встроенным, но пересмотренный ответ - нет. Это привело к тому, что указанные комментарии оказались неверными в отношении того, как работает код Мику. С тех пор ответ Мику был отредактирован для явного отображения как исходного, так и пересмотренного кода. Надеемся, что это ставит в тупик путаницу раз и навсегда.
Показать ещё 1 комментарий
Теги:
sh
scripting
boolean

12 ответов

705
Лучший ответ

Пересмотренный ответ (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/questions/2953646/how-to-declare-and-use-boolean-variables-in-shell-script

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

От: Использование булевых переменных в Bash

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

  • 22
    О, не бери в голову; Я понял. Это: если! Переменные $; тогда ... фи
  • 35
    Для того, чтобы объяснить , что происходит: if оператор выполняет содержимое переменной , которая является Bash встроенной true . Любая команда может быть установлена как значение переменной, и ее выходное значение будет оценено.
Показать ещё 13 комментариев
512

TL; DR

bool=true

if [ "$bool" = true ]

Проблемы с Miku (оригинал) answer

Я не рекомендую принятый ответ 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) особенно непослушен, потому что он выполнит команду, содержащуюся в переменной (поэтому условие оценивает значение true для допустимых команд 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. В приведенных выше случаях вы должны получить предупреждение о том, что команда не найдена. Но мы все еще можем сделать лучше (см. Мои рекомендации внизу).

Также см. объяснение Майка Холта оригинального ответа Мику.

Проблемы с ответом Hbar

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

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

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

Вы ожидали бы, что приведенное выше условие будет оцениваться как 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 но ваш код будет более защитным.


Сноски

  • Ответ Miku с тех пор был отредактирован и больше не содержит (известных) недостатков.
  • Не исчерпывающий список.
  • Действительная команда в этом контексте означает команду, которая существует. Не имеет значения, используется ли команда правильно или неправильно. Например. man woman все равно будет считаться допустимой командой, даже если такой man-страницы не существует.
  • Для недействительных (несуществующих) команд Bash просто будет жаловаться, что команда не найдена.
  • Если вам нужна длина, первая рекомендация самая короткая.
  • 7
    Использование == с [ или test не является переносимым. Учитывая, что переносимость является единственным преимуществом [ / test имеет преимущество над [[ , придерживайтесь = .
  • 0
    @chepner спасибо, не стесняйтесь редактировать мой ответ :)
Показать ещё 6 комментариев
78

Похоже, что существует некоторая непонимание здесь bash builtin true, а точнее, о том, как bash расширяет и интерпретирует выражения внутри скобок.

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

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

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 'и' e '. Это. Там не было попытки вызвать команду или встроенную функцию с именем yeah, а также нет (в примере miku) всякая специальная обработка происходит, когда bash анализирует токен true. Это просто строка и абсолютно произвольная.

Обновление (2/19/2014): После того, как вы ответите в ответ на miku, теперь я вижу, откуда происходит путаница. Ответ Miku использует отдельные скобки, но фрагмент кода, к которому он ссылается, не использует скобки. Это просто:

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

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

Здесь bash делает в каждом случае:

Без скобок:

  • Разверните переменную $the_world_is_flat в строку "true".
  • Попытка проанализировать строку "true" как команду.
  • Найдите и запустите команду true (встроенный или /bin/true, в зависимости от версии bash).
  • Сравните код выхода команды true (всегда 0) с 0. Вспомните, что в большинстве оболочек код выхода 0 указывает на успех, а что-то еще указывает на сбой.
  • Поскольку код выхода равен 0 (успех), выполните оператор if then

Кронштейны:

  • Разверните переменную $the_world_is_flat в строку "true".
  • Разбирайте условно-условное выражение, которое теперь полностью расширено, которое имеет вид string1 = string2. Оператором = является оператор сравнения строк bash. Так что...
  • Проведите сравнение строк в "true" и "true".
  • Да, две строки были одинаковыми, поэтому значение условного значения истинно.
  • Выполните инструкцию if then.

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

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

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

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

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

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

Обновление (4/14/2014) Чтобы ответить на вопрос в комментариях относительно разницы между = и ==: AFAIK, нет никакой разницы. Оператор == представляет собой bash -специфический синоним для =, и, насколько я понял, они работают точно так же во всех контекстах. Обратите внимание, однако, что я специально говорю о операторах сравнения строк = и ==, используемых в тестах [ ] или [[ ]]. Я не предполагаю, что = и == взаимозаменяемы всюду в bash. Например, вы, очевидно, не можете выполнять назначение переменной с помощью ==, например var=="foo" (технически вы можете это сделать, но значение var будет "=foo", потому что bash не видит a == здесь, он видит оператор = (присваивание), за которым следует буквальное значение ="foo", которое просто становится "=foo").

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

  • 0
    Подход без скобок также имеет то преимущество, что позволяет писать чистые, понятные (imo) однострочные $the_world_is_flat && echo "you are in flatland!" такие как $the_world_is_flat && echo "you are in flatland!"
  • 7
    Правда. Хотя я не сторонник (или против) любого подхода. Я просто хотел прояснить некоторую дезинформацию, за которую здесь голосуют, чтобы люди, которые позже наткнулись на эту тему, не ушли с кучей неправильных представлений о том, как все это работает.
Показать ещё 7 комментариев
37

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

#!/bin/bash

false=0
true=1

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

Вывод:

верно
  не false

  • 3
    Плюсы: (1.) поведение схоже с тем, как C обрабатывает bools, (2.) синтаксис очень лаконичен / минимален (не требует правой переменной и таких операторов, как '=' или '=='), (3 .) <субъективно> для меня я понимаю, что происходит без длинного многословного объяснения ... в отличие от ответов Мику и Денниса, которые, похоже, требуют длинных многословных объяснений </ subjective>
  • 2
    @TrevorBoydSmith Почему вы просто не сказали «плюсы: все, минусы: ничего». Сэкономит амортизационные отчисления на клавиатуре и мониторе в долгосрочной перспективе.
Показать ещё 8 комментариев
11

Давным-давно, когда все, что у нас было, было sh, booleans, где обрабатывается, полагаясь на соглашение программы test, где test возвращает статус ложного выхода, если запускается без аргументов. Это позволяет думать о переменной, которая не задана как false, а переменная установлена ​​на любое значение как значение true. Сегодня тест построен на 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

Из-за цитирования соглашения script авторы предпочитают использовать составную команду [[, которая имитирует 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

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

  • 3
    [ это не просто псевдоним внутри bash . Этот псевдоним также существует в виде двоичного файла (или в виде ссылки, указывающей на него) и может использоваться с голым sh . Проверьте ls -l /usr/bin/\[ . С bash / zsh вы должны вместо этого использовать [[ это действительно чистый внутренний и намного более мощный.
  • 1
    @dolmen [ и test также является BASH SHELL BUILTIN COMMAND в соответствии со страницей руководства Bash, поэтому проблем с производительностью не должно быть. То же самое, например, с Dash. (/ bin / sh может просто символическая ссылка на / bin / dash). Чтобы использовать исполняемый файл, вы должны использовать полный путь, то есть /usr/bin/\[ .
9

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

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

Итак, ответ довольно ясен. В bash нет boolean variable. Однако:

Используя оператор declare, мы можем ограничить присвоение значения переменным. [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 используется для явного указания, что переменные только для чтения. Надеюсь, что цель понятна.

  • 1
    Почему бы вам просто не declare -ir false=0 true=1 ? В чем преимущество использования массива?
  • 0
    @BenjaminW. Я просто хотел упомянуть о команде r option & readonly . Я бы сделал это так, как вы предложили в моих сценариях
Показать ещё 3 комментария
5

Вместо того, чтобы подделывать логическое значение и оставлять ловушку для будущих читателей, почему бы просто не использовать лучшее значение, чем 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
  • 0
    почему не целые числа?
  • 2
    @Blauhirn, потому что целые числа используются по-разному в зависимости от языков. В некоторых языках 0 приводит к false и 1 к true . Что касается кодов завершения программы (которые исторически использует bash), то это 0 для положительного результата или true а все остальное - отрицательный / ошибка или false .
2

Билл Паркер получает проголосовали за то, что его определения были отменены из обычного соглашения о коде. Обычно true определяется как 0, а false определяется как ненулевое значение. 1 будет работать для false, как и 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 -ef true]] = success, но также [[0 -eq false]] = успех, что неверно (hmmm.... как насчет того встроенного в числовое значение?).

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

Существуют и другие перестановки условного выражения, которые также дадут неверный вывод. В принципе, все (кроме указанного выше условия ошибки), которое устанавливает переменную в числовое значение и сравнивает ее с построением true/false или устанавливает переменную в значение true/false builtin и сравнивает ее с числовым значением. Кроме того, все, что устанавливает переменную в true/false builtin и делает сравнение с использованием -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

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

#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

Там у вас есть.

  • 1
    Встроенные модули true / false здесь не используются (игнорируйте, что может означать подсветка синтаксиса некоторых редакторов), особенно в случаях […] вы можете рассматривать здесь как простую строку (такую, которая задается как параметр в [ команду).
1

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

Те не являются булевыми в bash

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

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

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

if true; then ...

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

if COMMAND; then ...

Условие истинно, когда команда возвращает код выхода 0.

Вы могли бы так же хорошо:

if which foo; then echo "program foo found"; fi

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

if [[ 1 == 1 ]]; then echo yes; 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" ...

DO NOT

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}" != "true" ]]; then ...  # creates impression of booleans. Better compare against "false" here
if [[ "${var}" -eq "true" ]]; then ...  # `-eq` is for numbers and doesn't read as easy as `==`
  • 0
    Я предпочитаю использовать T и F чтобы прояснить, что это не настоящие логические значения.
1

Доброе утро, нежные люди. Я нашел существующие альтернативы запутанными. Лично я просто хочу иметь что-то, что выглядит и работает как C. Это то, что сработало для меня.

 #
 snapshotEvents=true
    :
 if ($snapshotEvents); then
    #  do stuff
 fi

и чтобы все были счастливы, я тестировал:

 #
 snapshotEvents=false
    :
 if !($snapshotEvents); then
    #  do else stuff
 fi

Что тоже отлично работает.

$snapshotEvents оценивает значение (содержимое) переменной. Вам не нужны скобки, я просто нахожу их полезными.

  • 2
    Там, где вы убираете скобки, это точно оригинальный ответ @ miku вверху.
  • 1
    Без скобок выражение не оценивается.
Показать ещё 2 комментария
0

Ниже приведено улучшение исходного ответа @miku, в котором рассматриваются проблемы @dennis относительно случая, когда переменная не задана:

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

Проверить, является ли переменная ложной:

if ! ${the_world_is_flat:-false} ; then
    echo 'Be careful not to fall off!'
fi

О других случаях (неприятный контент в переменной), это проблема с любым внешним вводом, поданным в программу. Перед тем, как доверять ему, должен быть проверен любой внешний вход. Но эта проверка должна быть выполнена только один раз, когда этот ввод будет получен. Это не должно влиять на производительность программы, делая это при каждом использовании переменной, как предлагает @dennis.

-3

Bash действительно смущает проблему с подобными [, [[, ((, $((и т.д. все шаги на кодовых пространствах друг друга). Я предполагаю, что это в основном исторический, где bash должен притворяться Иногда.

В большинстве случаев я могу просто выбрать метод и придерживаться его. В этом случае я склонен объявлять (желательно в общем файле библиотеки, который я могу включить в мои фактические сценарии)

TRUE=1;FALSE=0

Затем я могу использовать ((арифметический)) оператор компаратора для проверки...

testvar=$FALSE
if [[ -d ${does_directory_exist} ]]; then testvar=$TRUE; fi

if (( testvar == TRUE )); then
   # do stuff 'cos directory does exist
.
.
.
fi
  • Вам нужно быть дисциплинированным, ваш тествар должен быть установлен на $ TRUE или $FALSE в любое время
  • В (()) компараторах вам не нужны предыдущий $, что делает его более читаемым
  • Я могу использовать (()), потому что $TRUE = 1, $FALSE = 0, т.е. числовое
  • Недостатком является используйте $иногда
testvar=$TRUE
...which is not so pretty.

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

  • 2
    Вы должны объявить свои константы только для чтения. Также, пожалуйста, всегда используйте фигурные скобки при использовании переменных. Это соглашение, которое каждый должен придерживаться ИМХО. Большим недостатком этого решения является то, что вы не можете смешивать алгебраическое выражение с контрольными флагами или сравнениями строк.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню