Следующий код (протестированный с bash, zsh и ksh, результаты могут отличаться для других оболочек) возвращает a is 0 (num). То же самое происходит для a=''. Результаты назначения a=0 или a=1 предсказуемы. Цитирование выражений не меняет результат. Итак, почему двойные скобки рассматривают нулевые и пустые переменные как численно равные нулю?

unset a
#a=''
#a=0
#a=1
if [[ $a == 1 ]] ; then 
   echo 'a is 1 (string)' 
fi
if [[ "$a" == 0 ]] ; then 
   echo 'a is 0 (string)' 
fi
if [[ $a -eq 1 ]] ; then 
   echo 'a is 1 (num)' 
fi
if [[ "$a" -eq 0 ]] ; then 
   echo 'a is 0 (num)' 
fi

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

Дополнительные доказательства:

unset a ; if [[ $a -gt -1 ]] ; then echo 'a > -1' ; fi
unset a ; if [[ $a -lt  1 ]] ; then echo 'a <  1' ; fi
2
TTT 28 Янв 2020 в 22:53

3 ответа

Лучший ответ

Я не верю, что это явно задокументировано, но это оператор -eq, который вызывает квазиарифметический контекст для своих операндов. Рассматривать:

$ [[ "(3 + 5)" -eq 8 ]] && echo qed
qed

Поведение переменных задокументировано в разделе АРИФМЕТИЧЕСКАЯ ОЦЕНКА:

Переменная оболочки, которая является нулевой или неустановленной, имеет значение 0 при обращении по имени без использования синтаксиса расширения параметра.

Хотя не очевидно, что это также должно применяться к строке, являющейся результатом раскрытия параметра, и действительно, поведение отличается в арифметическом выражении или самой арифметической команде:

$ unset a
$ (( a == 0 )) && echo zero
zero
$ (( $a == 0 )) && echo zero
bash: ((: == 0 : syntax error: operand expected (error token is "== 0 ")

Учитывая, что у вас уже есть $((...)) и ((...)) для арифметики, лучше избегать -eq и других операторов арифметического сравнения внутри [[; либо используйте [ "$a" -eq 0 ] (что вызовет ошибку, если a равен нулю или не задано), либо используйте (( a == 0 ).

3
chepner 28 Янв 2020 в 20:05

Это хорошо задокументировано в руководстве: 6.5 Shell Arithmetic

В выражении на переменные оболочки также можно ссылаться по имени без использования синтаксиса раскрытия параметров. Переменная оболочки, имеющая значение NULL или не заданная, оценивается как 0 при ссылке по имени без использования синтаксиса раскрытия параметра.

А также

Нулевое значение оценивается как 0.

Ваши тесты относятся ко второму случаю.

Я не могу сказать вам обоснование дизайна этого. На практике это удобно:

unset a
(( a++ ))
echo $a    # => 1

Кроме того, другие языки делают то же самое:

$ awk 'BEGIN {if (unset_variable == 0) print "zero"}'
zero

$ perl -E 'if ($unset_variable == 0) {say "zero"}'
zero
2
glenn jackman 28 Янв 2020 в 20:04

Итак, почему двойные скобки рассматривают нулевые и пустые переменные как численно равные нулю?

Из руководства по оболочке bash 6.5. Арифметика оболочки:

Переменная оболочки, которая является нулевой или неустановленной, имеет значение 0 при обращении по имени без использования синтаксиса расширения параметра

Кавычки внутри составной команды расширения [[ bash не имеют значения, потому что внутри [[ раскрытие слов не выполняется.

0
KamilCuk 28 Янв 2020 в 20:04