Número de barras invertidas necessárias para escapar da barra invertida regex na linha de comandos

12

Recentemente, tive problemas com algumas expressões regulares na linha de comando e descobri que, para combinar uma barra invertida, diferentes números de caracteres podem ser usados. Esse número depende da citação usada para o regex (nenhum, aspas simples, aspas duplas). Veja a seguinte sessão do bash para entender o que quero dizer:

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

Isso significa que:

  • sem aspas, posso combinar uma barra invertida com 4-7 barras invertidas reais
  • com aspas duplas, posso combinar uma barra invertida com 3-6 barras invertidas reais
  • Com aspas simples, posso combinar uma barra invertida com 2-3 barras invertidas reais

Entendo que uma barra invertida extra é ignorada pelo shell (na página de manual do bash):

"Uma barra invertida não citada (\) é o caractere de escape. Ele preserva o valor literal do próximo caractere a seguir"

Isso não se aplica aos exemplos de aspas simples, porque nenhuma fuga é feita entre aspas simples.

E uma barra invertida adicional é ignorada pelo comando grep ("\ c" é apenas "c" escapado, mas é o mesmo que "c", porque "c" não tem um significado especial em uma regex).

Isso explica o comportamento do exemplo com aspas simples, mas eu realmente não entendo os outros dois exemplos, especialmente porque há uma diferença entre seqüências de caracteres entre aspas e aspas duplas.

Novamente, uma citação da página de manual do bash:

"A inclusão de caracteres entre aspas duplas preserva o valor literal de todos os caracteres dentro das aspas, com exceção de $,`, \ e, quando a expansão do histórico estiver ativada,!. "

Eu tentei o mesmo com o GNU awk (por exemplo awk /ab\cd/{print} file), com os mesmos resultados.

Perl, no entanto, mostra resultados diferentes (usando, por exemplo perl -ne "/ab\\cd/"\&\&print file):

  • sem aspas, posso combinar uma barra invertida com 4-5 barras invertidas reais
  • com aspas duplas, posso combinar uma barra invertida com 3-4 barras invertidas reais
  • Com aspas simples, posso combinar uma barra invertida com duas barras invertidas reais

Alguém pode explicar essa diferença entre as seqüências de caracteres regex não citadas e duplas na linha de comando para grep e awk? Eu não estou tão interessado em uma explicação do comportamento de Perl, já que geralmente não uso uma linha do Perl.

daniel kullmann
fonte

Respostas:

10

Para o exemplo não citado, cada \\par passa uma barra invertida para grep, portanto, quatro barras invertidas passam duas para grep, o que se traduz em uma única barra invertida. 6 barras invertidas passam três para grep, traduzindo para uma barra invertida e uma \cque é igual a c. Uma barra invertida adicional não altera nada, porque é traduzida \c-> cpelo shell. Oito barras invertidas no shell são quatro em grep, traduzidas para duas, portanto, isso não corresponde mais.

Para o exemplo entre aspas duplas, observe o que segue sua segunda citação na página de manual do bash:

A barra invertida mantém seu significado especial somente quando seguido por um dos seguintes caracteres: $, `,", \ ou nova linha.

Ou seja, quando você fornece um número ímpar de barras invertidas, a sequência termina em \c, o que seria igual ao ccaso não citado, mas, quando citada, a barra invertida perde seu significado especial e \cé passada para grep. É por isso que o intervalo de barras invertidas "possíveis" (ou seja, aquelas que compõem um padrão que corresponde ao seu arquivo de exemplo) desliza um por um.

Ansgar Esztermann
fonte
... e então existem algumas esquisitices: por exemplo: printf "\ntest"inserirá uma nova linha antes de "teste", mesmo que "\n"deva ter sido traduzida para "n"o shell como está entre aspas duplas ... (então o resultado esperado deve ser, por "\ ntest", "ntest". Deveríamos ter o hábito de escrever: printf "\\ntest"or printf '\ntest', mas de alguma forma eu vejo muitos scripts confiando na estranheza.
Olivier Dulac
6

Este link descreveu citações e fugas do bash

Sua pergunta lida com as três primeiras seções.

  • Escape por caractere
  • Citação fraca "aspas duplas"
  • Citação forte 'aspas simples'
  • ANSI C, como citação de string
  • Cotação I18N / L10N (Internacionalização e Localização) .

Abaixo está um gráfico de como as seqüências as bashtransmitem grepe como grepas interpreta internamente.

Vamos primeiro olhar echo "#ab\\cd" > file.
No fraco entre aspas ("") "#ab\\cd", o \\é um escape \que é passado para fileum único literal \. Então, filecontém ab\cd

Agora, aos seus comandos: A tabela abaixo pode ajudar a ver o que realmente acontece a cada chamada. O *mostra os que correspondem ao conteúdo do arquivo. É realmente apenas uma questão de aplicar as regras de escape do bash, como na página da web, com particular atenção à resposta de daniel kullmann, onde ele se refere ao comportamento de escape em uma situação de citações fracas .

A barra invertida mantém seu significado especial somente quando seguido por um dos seguintes caracteres: $, `,", \ ou nova linha.


                            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
fonte