Количество обратных косых черт, необходимых для экранирования обратного слеша регулярных выражений в командной строке

12

Недавно у меня возникли проблемы с некоторым регулярным выражением в командной строке, и я обнаружил, что для сопоставления обратной косой черты можно использовать различное количество символов. Это число зависит от цитирования, используемого для регулярного выражения (нет, одинарные кавычки, двойные кавычки). Посмотрите следующую сессию Bash, что я имею в виду:

echo "#ab\\cd" > file
grep -E ab\cd file
grep -E ab\\cd file
grep -E ab\\\cd file
grep -E ab\\\\cd file
#ab\cd
grep -E ab\\\\\cd file
#ab\cd
grep -E ab\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\cd file
#ab\cd
grep -E ab\\\\\\\\cd file
grep -E "ab\cd" file
grep -E "ab\\cd" file
grep -E "ab\\\cd" file
#ab\cd
grep -E "ab\\\\cd" file
#ab\cd
grep -E "ab\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\cd" file
#ab\cd
grep -E "ab\\\\\\\cd" file
grep -E 'ab\cd' file
grep -E 'ab\\cd' file
#ab\cd
grep -E 'ab\\\cd' file
#ab\cd
grep -E 'ab\\\\cd' file

Это означает, что:

  • без кавычек я могу сопоставить обратную косую черту с 4-7 фактическими
  • с двойными кавычками я могу сопоставить обратную косую черту с 3-6 фактическими
  • С одинарными кавычками я могу сопоставить обратную косую черту с 2-3 фактическими.

Я понимаю, что одна дополнительная обратная косая черта игнорируется оболочкой (со страницы руководства bash):

«Обратная косая черта без кавычек (\) является escape-символом. Она сохраняет буквальное значение следующего последующего символа»

Это не относится к примерам в одинарных кавычках, потому что в одинарных кавычках не выполняется экранирование.

И еще одна обратная косая черта игнорируется командой grep («\ c» - это просто «c», но это то же самое, что и «c», потому что «c» не имеет специального значения в регулярном выражении).

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

Опять цитата из справочной страницы bash:

«Заключение символов в двойные кавычки сохраняет буквальное значение всех символов в кавычках, за исключением $,`, \ и, когда расширение истории включено,!. »

Я попробовал то же самое с GNU awk (например awk /ab\cd/{print} file), с теми же результатами.

Perl, однако, показывает разные результаты (используя, например perl -ne "/ab\\cd/"\&\&print file):

  • без кавычек я могу сопоставить обратную косую черту с 4-5 фактическими
  • с двойными кавычками я могу сопоставить обратную косую черту с 3-4 фактическими
  • С одинарными кавычками я могу сопоставить обратную косую черту с двумя фактическими

Может ли кто-нибудь объяснить разницу между строками регулярных выражений без кавычек и двойными кавычками в командной строке для grep и awk? Меня не интересует объяснение поведения Perl, поскольку я обычно не использую однострочные символы Perl.

Даниэль Куллманн
источник

Ответы:

10

Для примера без кавычек каждая \\пара передает один обратный слеш в grep, поэтому 4 обратных слеша передают два обратному слешу, что переводится в один обратный слеш. 6 обратных слэшей передают три в grep, переводя в один обратный слеш и один \c, равный c. Одна дополнительная обратная косая черта ничего не меняет, потому что она переводится \c-> cоболочкой. Восемь обратных слешей в оболочке - четыре в grep, переведенные на два, так что это больше не соответствует.

Для примера в двойных кавычках, обратите внимание, что следует за вашей второй цитатой из man-страницы bash:

Обратная косая черта сохраняет свое специальное значение только тогда, когда за ней следует один из следующих символов: $, `,", \ или newline.

Т.е. когда вы даете нечетное количество обратных слешей, последовательность заканчивается на \c, что будет равно cв случае без кавычек, но при цитировании обратный слеш теряет свое особое значение, поэтому \cпередается grep. Вот почему диапазон «возможных» обратных слешей (то есть тех, которые составляют шаблон, соответствующий вашему файлу примера) уменьшается на единицу.

Ансгар Эстерманн
источник
... а затем есть некоторые странности: например: printf "\ntest"вставит новую строку перед "тестом", даже если оболочка "\n"должна была преобразовать "n"его в двойные кавычки ... (поэтому ожидаемый результат должен быть для "\ ntest", "ntest". У нас должна появиться привычка писать: printf "\\ntest"или printf '\ntest', но каким-то образом я вижу множество сценариев, полагающихся вместо этого на странность.
Оливье Дюлак
6

По этой ссылке описаны bash Quotes и Escapeing

Ваш вопрос касается первых трех разделов.

  • На персонажа
  • Слабое цитирование "двойные кавычки"
  • Сильные цитаты "одинарные кавычки"
  • ANSI C как строковое цитирование
  • Цитирование I18N / L10N (Интернационализация и локализация) .

Ниже приведен график того, как строки bashпередаются grepи как grepих интерпретировать внутренне.

Давайте сначала посмотрим на echo "#ab\\cd" > file.
В слабой кавычках ( «») "#ab\\cd", то \\это сбежавший , \который передается в fileкачестве одного литерала \. Итак, fileсодержит ab\cd

Теперь к вашим командам: приведенная ниже таблица может помочь увидеть, что на самом деле происходит с каждым вызовом. *Показывает те , которые соответствуют содержимому файла. На самом деле это просто вопрос применения правил эвакуации bash, как на веб-странице, с особой пометкой к ответу Даниэля Куллмана, где он ссылается на экранирование поведения в ситуации слабого цитирования .

Обратная косая черта сохраняет свое специальное значение только тогда, когда за ней следует один из следующих символов: $, `,", \ или newline.


                            bash passes    grep further
                            to grep        resolves to         
grep -E ab\cd file            abcd           abcd   
grep -E ab\\cd file           ab\cd          abcd  
grep -E ab\\\cd file          ab\cd          abcd
grep -E ab\\\\cd file         ab\\cd         ab\cd    * 
grep -E ab\\\\\cd file        ab\\\cd        ab\cd    *
grep -E ab\\\\\\cd file       ab\\\cd        ab\cd    *    
grep -E ab\\\\\\\cd file      ab\\\cd        ab\cd    *
grep -E ab\\\\\\\\cd file     ab\\\\cd       ab\\cd

grep -E "ab\cd" file          ab\cd          abcd
grep -E "ab\\cd" file         ab\cd          abcd
grep -E "ab\\\cd" file        ab\\cd         ab\cd    *
grep -E "ab\\\\cd" file       ab\\cd         ab\cd    *
grep -E "ab\\\\\cd" file      ab\\\cd        ab\cd    *
grep -E "ab\\\\\\cd" file     ab\\\cd        ab\cd    *
grep -E "ab\\\\\\\cd" file    ab\\\\cd       ab\\cd    

grep -E 'ab\cd' file          ab\cd          abcd  
grep -E 'ab\\cd' file         ab\\cd         ab\cd    *
grep -E 'ab\\\cd' file        ab\\\cd        ab\cd    *
grep -E 'ab\\\\cd' file       ab\\\\cd       ab\\cd
Peter.O
источник