Awk: gsub(«\\», «\\») дает удивительные результаты

#awk #escaping #gsub

#awk #экранирование #gsub

Вопрос:

Рассмотрим следующие входные данные:

 $ cat a
d:

$ cat a.awk
{ sub("\", "\\"); print $0 }

$ cat a_double.awk
{ sub("\\", "\\"); print $0 }
 

Теперь запуск cat a | awk -f a.awk дает

 d:
 

и запуск cat a | awk -f a_double.awk дает

 d:\
 

и я ожидаю ровно наоборот. Как я должен это интерпретировать?

 $ awk -V
GNU Awk 4.1.4, API: 1.1 (GNU MPFR 4.0.1, GNU MP 6.1.2)
 

Ответ №1:

Да, его ожидаемое поведение awk . Когда вы запускаете sub("\", "\\") свой первый скрипт, внутри sub " (двойные кавычки), поскольку мы НЕ используем / для сопоставления шаблон, нам нужно сначала экранировать (фактический буквальный символ), затем для экранирования, которое мы используем , поэтому нам также нужно экранировать это, следовательно, это станет \\

 \                                          \
|                                            |
|                                            |
first 2 chars are denoting escaping         next 2 chars are denoting actual literal character 
 

Чего НЕ происходит в вашем 1-м случае, следовательно, НЕТ совпадения, поэтому в нем нет замены, в вашем 2-м скрипте awk вы делаете это (экранирующая часть в разделе сопоставления регулярных sub выражений), следовательно, она идеально соответствует.


Давайте посмотрим на это на примере и попробуем поставить ... для целей проверки.

Когда ничего не происходит: поскольку нет совпадения на

 awk '{sub("\", "....\\"); print $0}' Input_file
d:
 

Когда происходит сопоставление шаблонов:

 awk '{sub("\\", "...\\"); print $0}' Input_file
d:...\
 

От man awk :

 gsub(r, s [, t])
For each substring matching the regular expression r in the string t,
 substitute the string s, and return the  number  of  substitutions.
 

Как мы могли бы выполнить фактическую экранирующую часть (где нам нужно использовать только перед символом только один раз)? Упомяните ваше регулярное /../ выражение в первом разделе sub like, и нам НЕ нужно здесь дважды экранировать.

 awk '{sub(/\/,"amp;\")} 1' Input_file
 

Ответ №2:

Первым аргументом *sub() является регулярное выражение, а не строка, поэтому вы должны использовать разделители regexp ( /.../ ), а не string ( "..." ) . Первое — это буквальное регулярное выражение, которое используется как есть, в то время как второе определяет динамическое (или вычисляемое) регулярное выражение, которое заставляет awk интерпретировать строку дважды: первый раз для преобразования строки в регулярное выражение, а второй — для использования ее в качестве регулярного выражения, следовательно, удваивает обратную косую черту, необходимую для экранирования. См. https://www.gnu.org/software/gawk/manual/gawk.html#Computed-Regexps .

В следующем нам просто нужно один раз экранировать обратную косую черту, потому что мы используем буквальное, а не динамическое регулярное выражение:

 $ cat a
d:

$ awk '{sub(/\/,"\\")}1' a
d:\
 

Ваш первый скрипт по праву выдал бы синтаксическую ошибку в более поздней версии gawk (5.1.0), поскольку "\" в динамическом регулярном выражении эквивалентно // в буквальном, и в этом выражении обратная косая черта экранирует последнюю косую черту, что означает отсутствие конечного разделителя:

 $ cat a.awk
{ sub("\", "\\"); print $0 }

$ awk -f a.awk a
awk: a.awk:1: (FILENAME=a FNR=1) fatal: invalid regexp: Trailing backslash: //