Здесь важно понимать, что в большинстве оболочек¹ [
— это обычная команда, анализируемая оболочкой, как и любая другая обычная команда.
Затем оболочка вызывает команду [
(, также известную как test
), со списком аргументов, после чего [
интерпретирует их как условное выражение.
В этот момент это просто список строк, и информация о том, какие из них являются результатом той или иной формы расширения, теряется даже в тех оболочках, где [
встроен -в (все Bourne -как в наши дни ).
Утилита [
раньше с трудом определяла, какие из ее аргументов были операторами, а какие операндами, (с которыми работают операторы ). Не помогло и то, что синтаксис изначально был двусмысленным. Например:
[ -t ]
раньше был (и до сих пор присутствует в некоторых оболочках/ [
s )для проверки того, является ли стандартный вывод терминалом. [ x ]
является сокращением от[ -n x ]
:проверить, является ли x
не -пустой строкой (, поэтому вы можете увидеть конфликт с приведенным выше ). [
s, -a
и -o
могут быть унарными([ -a file ]
для доступный файл(теперь заменен на [ -e file ]
), [ -o option ]
для опция включена?)и бинарные операторы(и и или). Опять же, ! -a x
может быть либо and(nonempty("!"), nonempty("x"))
, либо not(isaccessible("x"))
. (
, )
и !
добавляют больше проблем. В обычных языках программирования, таких как C или perl
, в:
if ($a eq $b) {...}
Содержимое $a
или $b
никоим образом не будет восприниматься как операторы, поскольку условное выражение анализируется до того, как $a
и $b
расширяются. Но в снарядах, в:
[ "$a" = "$b" ]
Оболочка расширяет переменные сначала ². Например, если $a
содержит (
, а $b
содержит )
, все, что видит команда [
, это [
, (
, =
,)
и ]
аргументы. Значит ли это, что"(" = ")"
(являются (
и )
лексически равными )или( -n = )
(является =
не -пустой строкой ).
Исторические реализации(test
появились в Unix V7 в конце 70-х годов )раньше давали сбой даже в тех случаях, когда это не было неоднозначно только из-за порядка, в котором они обрабатывали свои аргументы.
Здесь с версией 7 Unix в эмуляторе PDP11:
$ ls -l /bin/[
-rwxr-xr-x 2 bin 2876 Jun 8 1979 /bin/[
$ [ ! = x ]
test: argument expected
$ [ "(" = x ]
test: argument expected
Большинство оболочек и [
реализаций имеют или имели проблемы с ними или их вариантами . С bash
4.4 сегодня:
bash-4.4$ a='(' b=-o c=x
bash-4.4$ [ "$a" = "$b" -o "$a" = "$c" ]
bash: [: `)' expected, found =
POSIX.2 (, опубликованный в начале 90-х ), разработал алгоритм , который сделает поведение [
однозначным и детерминированным при передаче не более 4 аргументов (рядом с [
и]
)в наиболее распространенных шаблонах использования ([ -f "$a" -o "$b" ]
, которые еще не указаны, например, ). Он устарел (
, )
, -a
и -o
и удалил -t
без операнда. bash
реализовал этот алгоритм (или, по крайней мере, попытался )в bash
2.0.
Таким образом, в POSIX-совместимых реализациях [
[ "$a" = "$b" ]
гарантированно сравнивает содержимое $a
и $b
на предмет равенства, какими бы они ни были. Без -o
мы бы написали:
[ "$a" = "$b" ] || [ "$a" = "$c" ]
То есть вызовите [
дважды, каждый раз с менее чем 5 аргументами.
Но потребовалось некоторое время, чтобы все [
реализации стали совместимыми. bash
не был совместим до 4.4 (, хотя последняя проблема была для [ '(' ! "$var" ')' ]
, которую никто не использовал в реальной жизни)
Оболочка /bin/sh
Solaris 10 и старше, которая не является оболочкой POSIX, а является оболочкой Bourne, по-прежнему имеет проблемы с[ "$a" = "$b" ]
:
$ a='!' b='!'
$ [ "$a" = "$b" ]
test: argument expected
Использование [ "x$a" = "x$b" ]
решает проблему, поскольку нет оператора [
, который начинается с x
. Другой вариант — использовать case
вместо :
.
case "$a" in
"$b") echo same;;
*) echo different;;
esac
(цитирование необходимо вокруг $b
, а не вокруг$a
).
В любом случае,речь не идет и никогда не шла о пустых значениях. У людей возникают проблемы с пустыми значениями в [
, когда они забывают заключать свои переменные в кавычки, но тогда это не проблема с [
.
$ a= b='-o x'
[ $a = $b ]
со значением по умолчанию $IFS
становится:
[ = -o x ]
Это проверка того, является ли =
или x
непустой строкой -, но никакие префиксы не помогут³, так как [ x$a = x$b ]
по-прежнему будет :[ x = x-o x ]
, что вызовет ошибку, и это может стать намного хуже, включая DoS и ввод произвольной команды с другими значениями, как вbash
:
bash-4.4$ a= b='x -o -v a[`uname>&2`]'
bash-4.4$ [ x$a = x$b ]
Linux
Правильным решением будет всегда цитировать:
[ "$a" = "$b" ] # OK in POSIX compliant [ / shells
[ "x$a" = "x$b" ] # OK in all Bourne-like shells
Обратите внимание, что expr
имеет аналогичные (и даже более серьезные )проблемы.
expr
также имеет оператор =
, хотя он предназначен для проверки того, являются ли два операнда равными целыми числами, когда они выглядят как десятичные целые числа, или сортируются одинаково, если нет.
Во многих реализациях expr + = +
, expr '(' = ')'
или expr index = index
не выполняют сравнение на равенство. expr "x$a" = "x$b"
может обойти это для сравнения строк, но префикс x
может повлиять на сортировку (в локалях, которые имеют элементы сортировки, начинающиеся с x
, например ), и, очевидно, не могут использоваться для числа сравнение expr "0$a" = "0$b"
не работает для сравнения отрицательных целых чисел. expr " $a" = " $b"
работает для целочисленного сравнения в некоторых реализациях, но не в других (для a=01 b=1
, некоторые вернут true, некоторые false ).
¹ ksh93
является исключением. В ksh93
[
можно рассматривать как зарезервированное слово, поскольку [ -t ]
на самом деле отличается от var=-t; [ "$var" ]
или от ""[ -t ]
или cmd='['; "$cmd" -t ]
. Это сделано для того, чтобы сохранить обратную совместимость и по-прежнему соответствовать POSIX в тех случаях, когда это имеет значение. Здесь -t
воспринимается как оператор только в том случае, если он буквальный, а ksh93
определяет, что вы вызываете команду [
.
² ksh добавил [[...]]
оператор условного выражения с собственными правилами разбора синтаксиса (и некоторыми собственными проблемами )для решения проблем, которые (также встречаются в некоторых других оболочках, с некоторыми отличиями ).
³ за исключением zsh
, где split+glob не вызывается при расширении параметра, но пустое удаление по-прежнему вызывается, или в других оболочках при глобальном отключении split+glob с помощьюset -o noglob; IFS=