Автоматичне розширення змінної всередині команди bash [[]]


13

Під час перенаправлення змінної у bashви повинні використовувати $знак. Тим не менш, здається, що наступне працює просто чудово:

x=5
[[ x -gt 2 ]]

Хтось може це пояснити?

Редагувати: (детальніше)

Що я маю на увазі, як і чому команда [[]] відмінює мою змінну x без знака $. І так, якщо x = 1, оператор оцінюється як false (статус повернення 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. */

    Отже, так, обидві сторони арифметичного оператора потрапляють (безпосередньо) в оцінку арифметичних виразів. Безпосередньо, ні buts, ні ifs.


На практиці:

 $ 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

Змінна назва назва xрозширюється (навіть без $).

Це не відбувається для […]z в zsh або bash (це в ksh).


Це те саме, що відбувається всередині $((…)):

 $ echo $(( x + 7 ))
 10

І, будь ласка, розумійте, що це (дуже) рекурсивно (за винятком тире та яшу):

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

А 😮

І досить ризиковано:

 $ 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.
фра-сан

Bash не документує цього ніде, де я можу знайти. У людині ksh93 є такий собі опис : Дозволені також такі застарілі арифметичні порівняння: exp1 -eq exp2 . Існує цей текст в testрозділі zshbuiltins людини арифметичних операторів очікують число аргументів , а не арифметичних виразів . Що підтверджує, що деякі аргументи трактуються як арифметичні вирази тестом, побудованим в умовах, не зазначених у цій цитаті. Я підтверджую вихідним кодом….…
NotAnUnixNazi

7

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

Поведінка змінних імен в арифметичному вираженні описано в арифметиці Shell :

Змінні оболонки допускаються як операнди; Розширення параметра виконується перед оцінкою виразу. У межах виразу на змінні оболонки також можна посилатись по імені без використання синтаксису розширення параметра. Змінна оболонки, яка є нульовою чи невідомою, оцінюється на 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]}

З іншого боку, =порівняння є збігом шаблонів і не передбачає арифметики, а також автоматичного розширення змінної, виконаного в арифметичному контексті (Conditional Constructs):

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

Отже, це помилково, оскільки рядки, очевидно, різні:

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

як це, навіть якщо числові значення однакові:

[[ 6 = 06 ]] 

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

x=6
[[ x = 6 ]]

Однак це дозволить розширити змінну, тому це правда:

x=6
[[ $x = 6 ]]

насправді не можна знайти частину документації, де сказано, що числові порівняння приймають арифметичні вирази. Підтвердження в коді .
NotAnUnixNazi

Найближчим є те, що в описі arg1 OP arg2сказано, що аргументи можуть бути натуральними чи від'ємними цілими числами, що, напевно, повинно означати, що вони трактуються як арифметичні вирази. Збиваючи з пантелику, це також означає, що вони не можуть бути нульовими. :)
Бармар

@Barmar, е-е, правильно. Але це стосується і числових порівнянь [, і там вони не є арифметичними виразами. Натомість Баш скаржиться на нецілі числа.
ilkkachu

@ilkkachu [- це зовнішня команда, вона не має доступу до змінних оболонок. Його часто оптимізують за допомогою вбудованої команди, але вона все одно поводиться так само.
Вармар

@Barmar, я мав на увазі те, що у фрази "Arg1 і arg2 можуть бути додатні чи від'ємні цілі числа". з'являється в Bash Conditional изрази , і цей список стосується [так само добре [[. Навіть при [цьому операнди -eqта друзі мають / повинні бути цілими числами, так що цей опис також застосовується. Прийняття "повинен бути цілими числами" означати "інтерпретуються як арифметичні вирази" не застосовується в обох випадках. (Можливо, принаймні частково через те [, що ти, як ти кажеш, діє як звичайна команда.)
ilkkachu

1

Так, ваше спостереження правильне, змінне розширення виконується на виразах під подвійними дужками [[ ]], тому вам не потрібно ставити $перед іменем змінної.

Це прямо вказано в bashпосібнику:

[[вираз]]

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

Зауважте, що це не стосується односкладної версії [ ], як [це не ключове слово оболонки (синтаксис), а скоріше команда (в bash це вбудовано, інші оболонки можуть використовувати для тестування зовнішні, вишикувані).


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оператор очікує на кількість, тому ціле вираження буде переоцінено як би всередині (()), з іншого боку ==очікує рядки, тому замість цього запускається функція відповідності шаблону. Я не копався у вихідному коді, але звучить розумно.
jimmij

[- оболонка, вбудована в баш.
Нізам Мохамед

1
@NizamMohamed Це вбудований, але це все ще не ключове слово.
Кусалаланда
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.