Ошибка в тесте скобки оболочки, когда строка является левой скобкой

27

Раньше я был уверен в том, что цитирование строк - это всегда хорошая практика, чтобы избежать его разбора оболочкой.

Потом я наткнулся на это:

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

Попытка изолировать проблему, получая ту же ошибку:

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

Я решил проблему следующим образом:

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

И все же мне нужно знать, что здесь происходит.

Claudio
источник
2
В качестве обходного пути вы можете использовать bash[[ "$x" = '1' && "$y" = '1' ]]
terdon
3
Спецификация POSIX для теста явно описывает -aи -oустарела по этой причине (именно это означает [OB]верхний индекс рядом с их спецификацией). Если бы вы написали [ "$x" = 1 ] && [ "$y" = 1 ]вместо этого, у вас все было бы хорошо, и было бы хорошо в рамках четко определенного / стандартизированного поведения.
Чарльз Даффи
6
Вот почему люди использовали [ "x$x" = "x1" ]для предотвращения неправильного толкования аргументов как операторов.
Джонатан Леффлер
@JonathanLeffler: Эй, ты сжал мой ответ до одного предложения, не справедливо! :) Если кто-то использует оболочку POSIX, dashа не Bash, это все еще полезная практика.
Номинальное животное
Я хочу поблагодарить всех, кто нашел время, чтобы ответить на мой вопрос, очень ценю, ребята :)! Я также хочу поблагодарить вас за жизненно важное редактирование моего вопроса. Вход в этот форум иногда вызывает у меня то же волнение, что и бегство из Алькатраса, неправильный ход означает твою жизнь. Во всяком случае , я действительно хочу , чтобы моя Благодаря достигнет вас , прежде чем этот комментарий будет удален
Claudio

Ответы:

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и завершение ], понятно, что он не может разобрать, начинаются ли подвыражения с открытых скобок или нет, если задействована операция and . Конечно, синтаксический анализ в виде двух строковых сравнений возможен, но жадное выполнение этого может привести к проблемам при применении к правильным выражениям с вложенными выражениями в скобках.

Проблема, действительно, заключается в том, что [встроенная оболочка ведет себя так же, как если бы она расширила значение 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сложен. Используйте только простые выражения и используйте оболочку операторов !, &&и ||объединить их.

AlexP
источник