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

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.


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!


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'

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.


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;};}"
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.