Expressão regular usando \\ vs using \

10

Porque

grep e\\.g\\. <<< "this is an e.g. wow"

e

grep e\.g\. <<< "this is an e.g. wow"

Faça a mesma coisa?

Se eu adicionar uma terceira barra, ela também terá o mesmo resultado. MAS, quando adiciono uma quarta barra, ela não funciona mais. Isso tem a ver com a pergunta de um exame antigo para uma aula. Ele perguntou se aquele com duas barras invertidas funcionaria para exibir a linha com "por exemplo". Eu originalmente pensei que não iria funcionar, mas tentei ter certeza e funcionou. Qual a explicação?

Wyatt Grant
fonte
Eu pensei que o bash aceitaria \\\.e daria grep, \.mas não. boa pergunta

Respostas:

9

Primeiro, observe que a barra única corresponde demais:

$ echo $'eegg \n e.g.' | grep e\.g\.
eegg
 e.g.

No que diz respeito ao Bash , um período de escape é o mesmo que um período. Bash passa o período para grep . Para grep, um período corresponde a qualquer coisa.

Agora, considere:

$ echo $'eegg \n e.g.' | grep e\\.g\\.
 e.g.
$ echo $'eegg \n e.g.' | grep e\\\.g\\\.
 e.g.
$ echo $'eegg \n e.g.' | grep e\\\\.g\\\\.
$

Quando o Bash vê uma barra dupla, é reduzido a uma barra única e passa para grep que, no primeiro dos três testes acima, vê, como queremos, uma barra antes de um período. Assim, isso faz a coisa certa.

Com uma barra tripla, o Bash reduz os dois primeiros a uma barra simples. Então vê \.. Como um período de escape não tem significado especial para o Bash, isso é reduzido para um período simples. O resultado é que o grep vê, como queremos, uma barra antes de um período.

Com quatro barras, o Bash reduz cada par a uma única barra. Bash passa para grep duas barras e um ponto. O grep vê as duas barras e um ponto final e reduz as duas barras a uma única barra literal . A menos que a entrada tenha uma barra literal seguida por qualquer caractere, não há correspondências.

Para ilustrar isso, lembre-se de que, entre aspas simples, todos os caracteres são literais. Assim, dadas as três linhas de entrada a seguir, o comando grep corresponde apenas na linha com a barra literal na entrada:

$ echo 'eegg
e.g.
e\.g\.' |  grep e\\\\.g\\\\.
e\.g\.

Resumo do comportamento de Bash

Para o Bash, as regras são

  • Duas barras são reduzidas a uma única barra.

  • Uma barra na frente de um caractere normal, como um ponto final, é apenas o caractere normal (ponto final).

Portanto:

$ echo \. \\. \\\. \\\\.
. \. \. \\.

Existe uma maneira simples de evitar toda essa confusão: na linha de comando do Bash, expressões regulares devem ser colocadas entre aspas simples. Dentro de aspas simples, Bash deixa tudo em paz.

$ echo '\. \\. \\\. \\\\.'  # Note single-quotes
\. \\. \\\. \\\\.
John1024
fonte
Pergunta: São necessárias duas barras invertidas para que o bash a visualize como uma barra invertida (uma é a sequência de escape, a outra é a barra invertida literal). Então, quando existem 3, o bash também trata o terceiro retardador como uma sequência de escape? Como não está escapando de nada, é descartado?
Franz Kafka
@DanielAmaya O terceiro é tratado como uma fuga para o personagem que se segue. No nosso caso, esse caractere é o período e, para o bash (diferentemente do grep), um período de escape é apenas um período simples. bash passa o período simples para grep.
John1024
@DanielAmaya Consulte a resposta atualizada para uma echodeclaração que ilustra o que o bash faz nesses casos.
John1024
2
@DanielAmaya Nos dois casos, o bash reduz as duas primeiras barras para uma única barra. O que resta é \.ou .. Para o bash, ambos são iguais: são equivalentes a um período simples. Portanto, no total, o que o bash entrega ao grep é o mesmo para os dois: uma barra simples seguida por um ponto.
John1024
1
Apenas uma pequena adição - o uso echonão é uma maneira muito confiável de testar o regexp devido a muitas implementações deste programa. Por exemplo, no meu zsh (eco embutido) echo \. \\. \\\. \\\\. \\\\\.. \. \. \. \., mas /bin/echo \. \\. \\\. \\\\. \\\\\.retorna . \. \. \\. \\.. Algo como printf "%s" ...é provavelmente o melhor caminho.
jimmij
4

A saída é a mesma apenas para sua string, mas em geral essas expressões regulares fazem coisas diferentes. Vamos modificar um pouco o seu exemplo adicionando o segundo padrão e,g,(com vírgulas), o terceiro e\.g\.(pontos), o quarto e\,g\,(vírgulas) e a -oopção grep para imprimir apenas as peças correspondentes.

  • No caso seguinte .combinar com qualquer char (aviso ''ao redor e.g., virei a isso mais tarde)

    $ grep -o 'e.g.' <<< grep -o 'e.g.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e.g.
    e,g,
  • Em seguida, escapamos .com barra invertida \, portanto, apenas o literal .será correspondido:

    $ grep -o 'e\.g\.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e.g.
  • Mas podemos escapar \com outro \, para que o literal \seja correspondido seguido por .(ou seja, qualquer caractere):

    $ grep -o 'e\\.g\\.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e\.g\.
    e\,g\,
  • Mas se queremos corresponder apenas \.não, \,então \é necessário outro para escapar do significado especial do ponto:

    $ grep -o 'e\\\.g\\\.' <<< 'this is an e.g. e,g, e\.g\. e\,g\,'
    e\.g\.

Agora, como você não usou o ''argumento grep, é necessário adicionar outras barras invertidas para escapar da interpretação do shell, portanto:

grep 'e\.g\.'     => grep e\\.g\\.
grep 'e\\.g\\.'   => grep e\\\\.g\\\\.  (each backslash has to be quoted separately)
grep 'e\\\.g\\\.' => grep e\\\\\\.g\\\\\\. (3 x 2 = 6 backslashes in total)
jimmij
fonte
3

Quando você faz um grep e\.g\., o shell está consumindo a barra invertida; portanto, você está fazendo um grep e.g., que corresponde. Quando você faz um grep e\\.g\\., o shell está novamente consumindo uma barra, e agora você está fazendo um grep e\.\g., que novamente corresponde. Agora, parece uma barra invertida no shell \\. Então, quando você tem \\, o primeiro é uma sequência de escape, o segundo é uma barra invertida literal. Quando você faz a grep e\\\.g\\\., ela continua sendo grep e\.\g., porque não existe uma sequência de escape ( \) antes da primeira \para torná-la literal \. Lembre-se \ \ é uma barra invertida e, portanto, grep e\\\\.\\\\gacaba sendo grep e\\.g\\., o que obviamente não corresponde.

Para ver como o shell está vendo o que você está fazendo, use echo (por exemplo, echo grep e\\.g\\. <<< "this is an e.g. wow"vs. echo grep e\\\\.g\\\\. <<< "this is an e.g. wow")

Franz Kafka
fonte
0

Os dois comandos produzem a mesma saída apenas para sua entrada, mas, caso contrário, são diferentes. Para entender o que está acontecendo, precisamos saber como o parâmetro é interpretado primeiro bashe depois por grep.

Escapando no bash

\é um caractere especial que cancela o significado especial do caractere a seguir, incluindo \ele próprio. Se o caractere a seguir não tiver significado especial, ele será passado sem alterações. Exemplos com comando e resultado:

  • echo \a: a- caractere comum escapado fornece o caractere
  • echo \\: \- caractere especial escapado fornece ao personagem
  • echo \\\a: \a- combinação especial, comum
  • echo \\\\: \\- combinação especial, especial

echoimprimirá a sequência resultante depois de a bashinterpretar. Mais informações: documentação do bash , hackers festança wiki , especificação POSIX .

.não tem um significado especial em bash. É um personagem comum para o shell. Abaixo estão as sequências relevantes para seus exemplos:

  • echo .: .
  • echo \.: .
  • echo \\.: \.
  • echo \\\.: \.
  • echo \\\\.: \\.

Solução mais simples para cadeias literais no bash

Para passar parâmetros literalmente, bashvocê pode usar 'escape de aspas simples . Entre aspas simples, você não precisa se preocupar com o significado especial dos caracteres, porque as aspas simples são o único caractere com um significado especial. Você pode inserir uma aspas simples depois de incluir a primeira parte da sequência. Exemplo
echo 'part1'\''part2': part1'part2

Regex em grep

\é um caractere de escape com significado semelhante ao de bash. .é um caractere especial que representa uma ocorrência única de qualquer caractere . Veja: POSIX regex , GNU grep regex . Exemplos de expressões regex:

  • .- corresponde a qualquer caractere como aou.
  • \.- corresponde apenas .literalmente

Seus exemplos

Na segunda linha de cada exemplo abaixo, você vai encontrar equivalente com aspas simples 'mostrando qual string literal é passado por bashao grep. Depois de grepexecutar, o escape do único caractere especial possível nos exemplos .corresponde a qualquer caractere. Na terceira linha, há uma descrição com a qual a expressão corresponde.

  • grep e.g. <<< "this is an e.g. wow"
    grep 'e.g.' <<< "this is an e.g. wow"
    equalquer personagem gqualquer personagem - combina e.g.e possivelmente outras strings comoeagb
  • grep e\.g\. <<< "this is an e.g. wow"
    grep 'e.g.' <<< "this is an e.g. wow"
    equalquer personagem gqualquer personagem - combina e.g.e possivelmente outras strings comoexgy
  • grep e\\.g\\. <<< "this is an e.g. wow"
    grep 'e\.g\.' <<< "this is an e.g. wow"
    e.g.literalmente - corresponde e.g.apenas
  • grep e\\\.g\\\. <<< "this is an e.g. wow"
    grep 'e\.g\.' <<< "this is an e.g. wow"
    e.g.literalmente - corresponde e.g.apenas
  • grep e\\\\.g\\\\. <<< "this is an e.g. wow"
    grep 'e\\.g\\.' <<< "this is an e.g. wow"
    e\qualquer caractere g\qualquer caractere - não correspondee.g.
pabouk
fonte