Regex lookahead para 'não seguido por' no grep

103

Estou tentando usar o grep para todas as instâncias de Ui\.não seguido por Lineou mesmo apenas a letraL

Qual é a maneira correta de escrever um regex para localizar todas as instâncias de uma determinada string NÃO seguida por outra string?

Usando lookaheads

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing
Lee Quarella
fonte
5
Quais subespécies de regex - PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ...?
Jonathan Leffler
4
Como um aparte, o "evento não encontrado" vem do uso de expansão de história. Você pode querer desligar a expansão do histórico se nunca o usar e, às vezes, pode querer usar um ponto de exclamação em seus comandos interativos. set +o histexpandem Bash ou set +HYMMV.
tripleee
12
Eu também tive o problema de expansão da história. Eu acho que eu resolvi simplesmente mudando para aspas simples, de modo que o shell não tentaria munge o argumento.
Coderer
@Coderer que resolveu meu problema também. Obrigado.
NHDaly de

Respostas:

151

Antecipação negativa, que é o que você procura, requer uma ferramenta mais poderosa do que o padrão grep. Você precisa de um grep habilitado para PCRE.

Se você tem GNU grep, a versão atual suporta opções -Pou --perl-regexpe você pode usar o regex que deseja.

Se você não tem (uma versão suficientemente recente do) GNU grep, considere obter ack.

Jonathan Leffler
fonte
37
Tenho certeza de que o problema neste caso é apenas que no bash você deve usar aspas simples, não aspas duplas, para não tratar !como um caractere especial.
NHDaly
(veja abaixo minha resposta descrevendo exatamente isso.)
NHDaly
4
A resposta verificada e correta deve combinar esta resposta e o comentário de @NHDaly. Por exemplo, este comando funciona para mim: grep -P '^. * Contains ((?! But_not_this).) * $' * .Log. *> "D: \ temp \ result.out"
wangf
3
Para aqueles que -Pnão é suportado resultado tubulação tentar novamente grep --invert-match, ex: git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. Certifique-se de votar positivamente na resposta de @Vinicius Ottoni.
Daniel Sokolowski
@wangf Estou usando o Bash no Cygwin e quando mudo para aspas simples, ainda recebo o erro "evento não encontrado".
SSilk
40

A resposta para parte do seu problema está aqui, e ack se comportaria da mesma maneira: Ack & lookahead negativo dando erros

Você está usando aspas duplas para grep, o que permite ao bash "interpretar !como comando de expansão de histórico".

Você precisa envolver seu padrão em CITAÇÕES ÚNICAS: grep 'Ui\.(?!L)' *

No entanto, consulte a resposta de @JonathanLeffler para resolver os problemas com antecipações negativas no padrão grep!

NHDaly
fonte
Você está confundindo a funcionalidade de extensão do GNU grepcom a funcionalidade do padrão grep, onde o padrão para grepé POSIX. O que você diz também é verdade - executo o Bash com as barbáries do C-shell desativadas (porque se eu quisesse um C-shell, usaria um, mas não quero), então as !coisas não me afetam - mas para obter antecipações negativas, você precisa do não padrão grep.
Jonathan Leffler
1
@JonathanLeffler, obrigado pelo esclarecimento; Acho que você está certo ao dizer que ambas as nossas respostas são necessárias para tratar de todos os sintomas da OP. Obrigado.
NHDaly
11

Você provavelmente não pode executar lookaheads negativos padrão usando grep, mas normalmente você deve ser capaz de obter um comportamento equivalente usando a opção "inversa" '-v'. Usando isso, você pode construir um regex para o complemento do que deseja corresponder e, em seguida, canalizá-lo por meio de 2 greps.

Para a regex em questão, você pode fazer algo como

grep 'Ui\.' * | grep -v 'Ui\.L'
Karel Tucek
fonte
Isso excluiria mais coisas, mais instância se a linha contiver Ui.Line e Ui sem
.Line
1
(Sim, é por isso que não o formulo estritamente. Isso simplesmente resolve uma parte significativa dos cenários que levam as pessoas a esse problema, nada mais.)
Karel Tucek
4

Se você precisa usar uma implementação de regex que não suporta lookaheads negativos e não se importa em combinar caracteres extras *, você pode usar classes de caracteres negados[^L] , alternância| e âncora de fim de string$ .

No seu caso grep 'Ui\.\([^L]\|$\)' *faz o trabalho.

  • Ui\. corresponde à string em que você está interessado

  • \([^L]\|$\)corresponde a qualquer caractere único diferente de Lou corresponde ao final da linha: [^L]ou $.

Se você quiser excluir mais do que apenas um caractere, basta lançar mais alternância e negação nele. Para encontrar anão seguido por bc:

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

Que é ( aseguido por não bou seguido pelo final da linha: aentão [^b]ou $) ou ( aseguido por bque é seguido por não cou é seguido pelo final da linha: aentão b, então [^c]ou $.

Este tipo de expressão torna-se bastante pesado e sujeito a erros, mesmo com uma string curta. Você poderia escrever algo para gerar as expressões para você, mas provavelmente seria mais fácil usar apenas uma implementação de regex que suporte lookaheads negativos.

* Se sua implementação oferecer suporte a grupos de não captura , você pode evitar a captura de caracteres extras.

dougcosina
fonte
1

Se o seu grep não suporta -P ou --perl-regexp, e você pode instalar o grep habilitado para PCRE, por exemplo, "pcregrep", então ele não precisará de nenhuma opção de linha de comando como GNU grep para aceitar o padrão compatível com Perl expressões, você acabou de correr

pcregrep "Ui\.(?!Line)"

Você não precisa de outro grupo aninhado para "Linha" como em seu exemplo "Ui. (?! (Linha))" - o grupo externo é suficiente, como mostrei acima.

Deixe-me dar outro exemplo de como procurar asserções negativas: quando você tem uma lista de linhas, retornada por "ipset", cada linha mostrando o número de pacotes no meio da linha, e você não precisa de linhas com pacotes zero, você apenas corre:

ipset list | pcregrep "packets(?! 0 )"

Se você gosta de expressões regulares compatíveis com perl e tem perl, mas não tem pcregrep ou seu grep não suporta --perl-regexp, você pode criar scripts perl de uma linha que funcionam da mesma maneira que grep:

perl -e "while (<>) {if (/Ui\.(?!Lines)/){print;};}"

Perl aceita stdin da mesma forma que grep, por exemplo

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
Maxim Masiutin
fonte