Помилка тесту дужки оболонки, коли рядок є лівою дужкою


27

Я раніше був впевнений у тому, що цитування рядків - це завжди хороша практика, щоб уникнути розбору оболонки.

Потім я натрапив на це:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

Намагаючись ізолювати проблему, отримуючи ту ж помилку:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

Я вирішив проблему так:

[ "$x" = '1' ] && [ "$y" = '1' ]

Ще мені потрібно знати, що тут відбувається.


2
Як вирішення, в [[ "$x" = '1' && "$y" = '1' ]]
баші

3
Специфікація POSIX для тесту явно описує -aта -oє застарілою з цієї причини (що означає [OB]надширочний сценарій поруч із їх специфікацією). Якби ви писали [ "$x" = 1 ] && [ "$y" = 1 ]замість цього, ви будете добре, і будете добре в межах чітко визначеної / стандартизованої поведінки.
Чарльз Даффі

6
Ось чому люди використовували, [ "x$x" = "x1" ]щоб запобігти неправильному трактуванню аргументів як операторів.
Джонатан Леффлер

@JonathanLeffler: Гей, ви звеличили мою відповідь на одне речення, не справедливо! :) Якщо ви використовуєте оболонку POSIX, dashа не Bash, це все ще корисна практика.
Номінальна тварина

Хочу подякувати всім, хто знайшов час, щоб відповісти на моє запитання, дуже вдячні хлопці :)! Я також хочу подякувати за життєво важливе редагування мого питання. Вступ на цей форум іноді викликає в мене той самий трепет від втечі з Алькатраса, неправильний хід означає ваше життя. У будь-якому разі я дуже хочу, щоб моя подяка надійшла до вас, перш ніж цей коментар буде видалений
Клаудіо

Відповіді:


25

Це дуже незрозумілий кутовий випадок, що можна розглянути помилку в тому, як визначено [вбудований тест ; однак це відповідає поведінці фактичних [бінарних файлів, доступних у багатьох системах. Наскільки я можу судити, це зачіпає тільки деякі випадки , і змінну , що має значення , яке відповідає [оператору , як (, !, =, -eі так далі.

Дозвольте мені пояснити, чому і як обходити це в оболонках Bash та POSIX.


Пояснення:

Розглянемо наступне:

x="("
[ "$x" = "(" ] && echo yes || echo no

Без проблем; вищезгадане не дає помилок і виводить yes. Ось так ми очікуємо, що робота працюватиме. Ви можете змінити рядок порівняння, '1'якщо вам подобається, і значення x, і воно буде працювати, як очікувалося.

Зауважте, що власне /usr/bin/[бінарне поводиться так само. Якщо ви працюєте, наприклад '/usr/bin/[' '(' = '(' ']', помилок немає, оскільки програма може виявити, що аргументи складаються з однієї операції порівняння рядків.

Помилка відбувається , коли ми і з другим виразом. Не має значення, що таке другий вираз, доки воно дійсне. Наприклад,

[ '1' = '1' ] && echo yes || echo no

виводить yesі, очевидно, є коректним виразом; але, якщо ми поєднаємо два,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

Bash відкидає вираз , якщо і тільки якщо xє (або !.

Якби ми запускали вище, використовуючи фактичну [програму, тобто

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

помилка була б зрозумілою: оскільки оболонка робить замінники змінної, /usr/bin/[двійковий прийом приймає лише параметри ( = ( -a 1 = 1та закінчення ], вона, зрозуміло, не може розібратися, чи відкриті дужки починають підвираз, чи ні, там задіяна і операція. Зрозуміло, можливий аналіз цього порівняння як двох рядкових порівнянь, але робити це так жадібно, що може спричинити проблеми при застосуванні до правильних виразів із скобками в дужках.

Справді, проблема полягає в тому, що [вбудована оболонка поводиться так само, як ніби вона розширює значення, xперш ніж вивчати вираз.

(Ці двозначності та інші, пов’язані з розширенням змінної, були великою причиною того, чому Bash реалізував і тепер рекомендує [[ ... ]]замість цього використовувати тестові вирази.)


Вирішення проблеми тривіальне, і часто його можна побачити у сценаріях із використанням старих shоболонок. Ви додаєте "безпечний" символ часто xперед рядками (обидва значення порівнюються), щоб переконання виразу було визнано порівнянням рядків:

[ "x$x" = "x(" -a "x$y" = "x1" ]

1
Я б не називав поведінку [вбудованої помилки. Якщо що-небудь, це притаманний недолік дизайну. [[є ключовим словом оболонки , а не просто вбудованою командою, тому воно має переглядати речі перед видаленням цитат і фактично переосмислювати звичайне розділення слів. наприклад [[ $x == 1 ]], не потрібно використовувати "$x", оскільки [[контекст відрізняється від звичайного. У всякому разі, саме так [[можна уникнути підводних каменів [. POSIX вимагає [поводитись так, як це відбувається, і bash здебільшого сумісний з POSIX навіть без цього --posix, тому перетворення [на ключове слово непривабливо.
Пітер Кордес

POSIX не рекомендує вирішувати ваше рішення. Просто скористайтеся двома дзвінками [.
Wildcard

@PeterCordes: Цілком правильно; влучне зауваження. (Можливо, я мав би використовувати розроблений замість визначеного в моєму першому абзаці, але [поведінка обтяжена історією, що передує POSIX, я вибрав останнє слово замість цього.) Я особисто уникаю використання [[в моєму прикладі сценаріїв, але тільки тому, що це Виняток із рекомендації щодо цитування, про яку я завжди говорю (оскільки пропущення цитат є найпоширенішою причиною помилок скрипта, які я бачу), і я не придумав простий абзац, щоб пояснити, чому [[є виняток із правил, не роблячи моїх рекомендацій щодо цитування. підозрюваний.
Номінальна тварина

@Wildcard: Ні. POSIX не рекомендує проти такої практики , і саме це важливо. Тільки тому, що авторитет не рекомендує цю практику, не робить це поганим. Дійсно, як я зазначаю, це історична практика, що використовується в shсценаріях, що добре передує стандартизації POSIX. Використання подвійних виразів у дужках -aта -oлогічних операторів у тестах / [є більш ефективним, ніж посилання на ланцюги виразів (через &&та ||); Просто в сучасних машинах різниця не має значення.
Номінальна тварина

@Wildcard: Однак я особисто віддаю перевагу ланцюжкові тестові вирази, використовуючи &&і ||, але причина полягає в тому, що нам легше підтримувати (читати, розуміти та змінювати, якщо / коли потрібно) їх, не вводячи помилок. Отже, я не критикую вашу пропозицію, а лише міркування, що стоять за пропозицією. (Для дуже аналогічних причин .LT., .GE.і т.д. оператори порівняння в FORTRAN 77 отримали набагато більше легким для читання версії <, >=і т.д. в більш пізніх версіях.)
Номінальні тварини

11

[він testбачить:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testприймає субекспресії в дужках; тому він вважає, що ліва дужка відкриває піддекспресію і намагається її розібрати; парсер розглядає =як перше, що є в піддекспресії, і вважає, що це неявна перевірка довжини рядків, тому він щасливий; Підвираз повинен потім слідувати правою дужкою, а замість цього аналізатор знаходить 1замість ). І скаржиться.

Коли testмає рівно три аргументи, а середній аргумент є одним із розпізнаних операторів, він застосовує цього оператора до 1-го та 3-го аргументів, не шукаючи субекспресій у дужках.

Щоб переглянути детальну інформацію man bash, шукайте test expr.

Висновок: алгоритм розбору, який використовується, testє складним. Використовуйте тільки прості вирази та використовувати оболонки операторів !, &&і ||об'єднати їх.

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