Автоматическое расширение переменной внутри команды bash [[]]

13

При разыменовании переменной в bash, вы должны использовать $знак. Тем не менее, кажется, что следующее работает просто отлично:

x=5
[[ x -gt 2 ]]

Кто-нибудь может объяснить это?

Редактировать: (подробнее)

Я имею в виду, как и почему команда [[]] разыменовывает мою переменную x без знака $. И да, если x = 1, оператор оценивается как ложный (возвращаемый статус 1)

гость
источник
2
Что ты имеешь в виду под "работать просто отлично"? И изменится ли ваша оценка, если вы x=1последуете [[ x -gt 2]]?
Nohillside
Я имею в виду: как и почему команда [[]] разыменовывает мою переменную x без знака $. И да, если x = 1, утверждение ложно (возвращаемый статус 1)
Гость

Ответы:

9

Причина в том, что -eqсилы арифметической оценки аргументов.

Арифметический оператор: -eq, -gt, -lt, -ge, -leи -neвнутри [[ ]](в KSH, Zsh и Баш) средство для автоматического расширения имен переменных , как и в языке С, не нужно для ведущего $.

  • Для подтверждения мы должны заглянуть в исходный код bash. Руководство не предлагает прямого подтверждения.

    Внутри test.cобработки арифметические операторы попадают в эту функцию:

    arithcomp (s, t, op, flags)

    Где sи tоба операнда. Операнды передаются этой функции:

    l = evalexp (s, &expok);
    r = evalexp (t, &expok);

    Функция evalexpопределена внутри expr.c, которая имеет этот заголовок:

    /* expr.c -- arithmetic expression evaluation. */

    Итак, да, обе стороны арифметического оператора (напрямую) попадают в оценку арифметического выражения. Напрямую, нет, но если нет.


На практике с:

 $ x=3

Оба из этого терпят неудачу:

 $ [[ x = 4 ]] && echo yes || echo no
 no

 $ [[ x = 3 ]] && echo yes || echo no
 no

Что правильно, xне расширяется и xне равно числу.

Тем не мение:

 $ [[ x -eq 3 ]] && echo yes || echo no
 yes

 $ [[ x -eq 4 ]] && echo yes || echo no
 no

Переменная named xраскрывается (даже без $).

Это не происходит для […]zsh или bash (это происходит для ksh).


Это то же самое, что происходит внутри $((…)):

 $ echo $(( x + 7 ))
 10

И, пожалуйста, поймите, что это (очень) рекурсивно (кроме дефиса и тире):

 $ a=b b=c c=d d=e e=f f=3
 $ echo "$(( a + 7 ))" 
 10

A 😮

И довольно рискованно

 $ x='a[$(date -u)]'
 $ [[ x -eq 3 ]] && echo yes || echo no
 bash: Tue Dec  3 23:18:19 UTC 2018: syntax error in expression (error token is "Dec  3 23:18:19 UTC 2018")

Синтаксической ошибки можно легко избежать:

 $ a=3; x='a[$(date -u >/dev/tty; echo 0)]'

 $ [[ x -eq 3 ]] && echo yes || echo no
 Tue Dec  4 09:02:06 UTC 2018
 yes

Как говорится: дезинфицируйте ваш вклад

 $ [[ ${x//[^0-9]} -eq 3 ]] && echo yes || echo no
 no

конец 😮


И (более старый) внешний /usr/bin/test(не встроенный test), и еще более старый, а также внешний exprне расширяют выражения только целыми числами (и, по-видимому, только десятичными):

 $ /usr/bin/test "x" -eq 3
 /usr/bin/test: invalid integer x

 $ expr x + 3
 expr: non-integer argument
Исаак
источник
Интересный. Нетрудно сказать, как это возможно - будучи [[ключевым словом, операторы и операнды обнаруживаются при чтении команды, а не после раскрытия. Таким образом , [[можно рассматривать -eqв более умном , чем, скажем, [. Но вот что мне интересно: где мы можем найти документацию о логике, которую использует bash для интерпретации составных команд? Это не выглядит для меня очевидным, и я, очевидно, не могу найти удовлетворительных объяснений в manили info bash.
Фра-сан
Баш не документирует это нигде, где я могу найти. В man ksh93 есть своего рода описание : также допускаются следующие устаревшие арифметические сравнения: exp1 -eq exp2 . Существует этот текст в разделе zshbuiltins человека арифметических операторов ожидают число аргументов , а не арифметических выражений . Что подтверждает, что некоторые аргументы обрабатываются встроенными тестами как арифметические выражения в условиях, не указанных в этой цитате. Я подтвердлю с исходным кодом….…test
Исаак
7

Операнды численных сравнений -eq, -gt, -lt, -ge, -leи -neпринимаются в качестве арифметических выражений. С некоторыми ограничениями, они по-прежнему должны быть словами из одной оболочки.

Поведение имен переменных в арифметическом выражении описано в Shell Arithmetic :

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

а также:

Значение переменной оценивается как арифметическое выражение, когда на него ссылаются

Но я не могу найти ту часть документации, в которой говорится, что числовые сравнения принимают арифметические выражения. Он не описан в Условных конструкциях в разделе [[и не описан в Условных выражениях Bash .

Но, экспериментально, кажется, работает как сказано выше.

Итак, такие вещи работают:

a=6
[[ a -eq 6 ]] && echo y 
[[ 1+2+3 -eq 6 ]] && echo y
[[ "1 + 2 + 3" -eq 6 ]] && echo y

это тоже (значение переменной оценивается):

b='1 + 2 + 3'
[[ b -eq 6 ]] && echo y

Но это не так; это не одно слово оболочки при [[ .. ]]разборе, поэтому в условном выражении есть синтаксическая ошибка:

[[ 1 + 2 + 3 -eq 6 ]] && echo y

В других арифметических контекстах нет необходимости, чтобы выражение было без пробелов. Это печатает 999, поскольку скобки однозначно разграничивают арифметическое выражение в индексе:

a[6]=999; echo ${a[1 + 2 + 3]}

С другой стороны, =сравнение является сопоставлением с шаблоном и не включает арифметику и автоматическое расширение переменных, выполняемое в арифметическом контексте (условные конструкции):

Когда ==и !=операторы используются, строка справа оператора считается образцом и подобраны в соответствии с правилами , описанными ниже в Pattern Matching, как если опция extglob оболочки были включены. =Оператор идентичен ==.

Так что это неверно, поскольку строки явно разные:

[[ "1 + 2 + 3" = 6 ]] 

как это, даже если числовые значения одинаковы:

[[ 6 = 06 ]] 

и здесь тоже сравниваются строки ( xи 6), они разные:

x=6
[[ x = 6 ]]

Это расширило бы переменную, так что это правда:

x=6
[[ $x = 6 ]]
ilkkachu
источник
на самом деле не может найти ту часть документации, где говорится, что числовые сравнения принимают арифметические выражения. Подтверждение в коде .
Исаак
Наиболее близким является то, что в описании arg1 OP arg2говорится, что аргументы могут быть положительными или отрицательными целыми числами, что, как я предполагаю, предполагает, что они рассматриваются как арифметические выражения. Смущает, это также подразумевает, что они не могут быть нулем. :)
Бармар
@ Бармар, да, верно. Но это относится и к числовым сравнениям [, и там они не являются арифметическими выражениями. Вместо этого Баш жалуется на нецелые числа.
ilkkachu
@ilkkachu [- это внешняя команда, она не имеет доступа к переменным оболочки. Он часто оптимизируется с помощью встроенной команды, но ведет себя так же.
Бармар
@ Barmar, я имел в виду, что фраза «Arg1 и arg2 могут быть положительными или отрицательными целыми числами». появляется в условных выражениях Bash , и этот список относится к так [же, как и [[. Даже при [этом операнды -eqи друзья должны быть целыми числами, поэтому описание также применимо. Принятие «должны быть целыми числами» означает «интерпретируются как арифметические выражения» не применимо в обоих случаях. (Возможно, по крайней мере, отчасти из-за того [, что вы действуете как обычная команда, как вы говорите.)
ilkkachu
1

Да, ваши наблюдения верны, расширение переменных выполняется для выражений в двойных скобках [[ ]], поэтому вам не нужно ставить $перед именем переменной.

Это прямо указано в bashруководстве:

[[выражение]]

(...) Разделение слов и расширение пути не выполняются над словами между [[и]]; Выполняется расширение тильды, расширение параметров и переменных, арифметическое расширение, подстановка команд, подстановка процессов и удаление кавычек.

Обратите внимание, что это не версия с одной скобкой [ ], так как [это не ключевое слово оболочки (синтаксис), а команда (в bash она встроена, другие оболочки могут использовать внешние, выровненные для тестирования).

jimmij
источник
1
Спасибо за ответ. Кажется, это работает только для чисел. x = city [[$ x == city]] Это не работает без знака $.
Гость
3
Похоже, здесь есть еще что-то: (x=1; [[ $x = 1 ]]; echo $?)возврат 0, (x=1; [[ x = 1 ]]; echo $?)возврат 1, т.е. расширение параметров не выполняется xпри сравнении строк. Это поведение выглядит как арифметическая оценка, вызванная арифметическим расширением, то есть тем, что происходит в (x=1; echo $((x+1))). (Об арифметической оценке man bashговорится, что «Внутри выражения на переменные оболочки также можно ссылаться по имени без использования синтаксиса расширения параметров).
fra-san
@ fra-san Действительно, поскольку -gtоператор ожидает число, поэтому все выражение переоценивается так, как будто оно внутри (()), с другой стороны, ==ожидает строки, поэтому вместо этого запускается функция сопоставления с образцом. Я не копался в исходном коде, но звучит разумно.
Джимми
[это оболочка, встроенная в bash
Низам Мохамед
1
@NizamMohamed Это встроенный, но все еще не ключевое слово.
Кусалананда