Regex lookahead para 'no seguido por' en grep

103

Estoy intentando grep para todas las instancias de Ui\.no seguido Lineo incluso solo de la letraL

¿Cuál es la forma correcta de escribir una expresión regular para encontrar todas las instancias de una cadena en particular NO seguida de otra cadena?

Usando lookaheads

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


grep "Ui\.(?!(Line))" *
nothing
Lee Quarella
fuente
5
¿Qué subespecies de expresiones regulares: PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ...?
Jonathan Leffler
4
Como acotación al margen, el "evento no encontrado" proviene del uso de la expansión del historial. Es posible que desee desactivar la expansión del historial si nunca la usa y, a veces, desea poder usar un signo de exclamación en sus comandos interactivos. set +o histexpanden Bash o set +HYMMV.
tripleee
12
También tuve el problema de la expansión del historial. Creo que lo resolví simplemente cambiando a comillas simples, para que el shell no intentara mordisquear el argumento.
Coderer
@Coderer que también resolvió mi problema. Gracias.
NHDaly

Respuestas:

151

La búsqueda anticipada negativa, que es lo que busca, requiere una herramienta más poderosa que la estándar grep. Necesita un grep habilitado para PCRE.

Si tiene GNU grep, la versión actual admite opciones -Po --perl-regexpy luego puede usar la expresión regular que desee.

Si no tiene (una versión suficientemente reciente de) GNU grep, considere obtener ack.

Jonathan Leffler
fuente
37
Estoy bastante seguro de que el problema en este caso es que en bash debes usar comillas simples, no dobles, para que no se trate !como un carácter especial.
NHDaly
(vea a continuación mi respuesta que describe exactamente eso).
NHDaly
4
La respuesta correcta y verificada debe combinar esta respuesta y el comentario de @ NHDaly. Por ejemplo, este comando me funciona: grep -P '^. * Contiene ((?! But_not_this).) * $' * .Log. *> "D: \ temp \ result.out"
wangf
3
Para aquellos en los que -Pno es compatible resultado tuberías volver a intentar grep --invert-match, por ejemplo: git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. Asegúrate de votar la respuesta de @Vinicius Ottoni.
Daniel Sokolowski
@wangf Estoy usando Bash en Cygwin y cuando cambio a comillas simples, sigo recibiendo el error "evento no encontrado".
SSilk
40

La respuesta a parte de su problema está aquí, y ack se comportaría de la misma manera: Ack y búsqueda anticipada negativa dando errores

Está utilizando comillas dobles para grep, lo que permite que bash "interprete !como comando de expansión de historial".

Necesita envolver su patrón en Citas individuales: grep 'Ui\.(?!L)' *

Sin embargo, consulte la respuesta de @ JonathanLeffler para abordar los problemas con las búsquedas negativas en estándar grep.

NHDaly
fuente
Está confundiendo la funcionalidad de extensión de GNU grepcon la funcionalidad de estándar grep, donde el estándar para grepes POSIX. Lo que dices también es cierto: ejecuto Bash con los barbarismos de C-shell desactivados (porque si quisiera un shell C, usaría uno, pero no quiero uno), por lo que las !cosas no me afectan - pero para obtener vistas anticipadas negativas, necesita no estándar grep.
Jonathan Leffler
1
@JonathanLeffler, gracias por la aclaración; Creo que tiene razón en que se requieren nuestras dos respuestas para abordar todos los síntomas del OP. Gracias.
NHDaly
11

Probablemente no pueda realizar búsquedas estándar negativas usando grep, pero normalmente debería poder obtener un comportamiento equivalente usando el modificador "inverso" '-v'. Con eso, puede construir una expresión regular para el complemento de lo que desea hacer coincidir y luego canalizarlo a través de 2 greps.

Para la expresión regular en cuestión, puede hacer algo como

grep 'Ui\.' * | grep -v 'Ui\.L'
Karel Tucek
fuente
Eso excluiría más cosas, más ejemplo si la línea contiene Ui.Line y Ui sin .Line
nafg
1
(Sí, es por eso que no lo formulo estrictamente. Esto simplemente resuelve una parte significativa de los escenarios que llevan a las personas a este problema, nada más.)
Karel Tucek
4

Si necesita usar una implementación de expresiones regulares que no admita búsquedas anticipadas negativas y no le importa hacer coincidir caracteres adicionales *, entonces puede usar clases de caracteres negadas[^L] , alternancia| y el final del ancla de cadena$ .

En tu caso grep 'Ui\.\([^L]\|$\)' *hace el trabajo.

  • Ui\. coincide con la cadena que le interesa

  • \([^L]\|$\)coincide con cualquier carácter que no sea Lo coincide con el final de la línea: [^L]o $.

Si desea excluir más de un carácter, solo necesita agregar más alternancia y negación. Para encontrar ano seguido por bc:

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

Que es ( aseguido de no bo seguido del final de la línea: aentonces [^b]o $) o ( aseguido de bcuál es seguido de no co del final de la línea: aentonces b, entonces [^c]o $.

Este tipo de expresión llega a ser bastante difícil de manejar y propensa a errores incluso con una cadena corta. Podría escribir algo para generar las expresiones por usted, pero probablemente sería más fácil usar una implementación de expresiones regulares que admita búsquedas anticipadas negativas.

* Si su implementación admite grupos que no capturan, puede evitar capturar caracteres adicionales.

dougcosine
fuente
1

Si su grep no admite -P o --perl-regexp, y puede instalar grep habilitado para PCRE, por ejemplo, "pcregrep", entonces no necesitará ninguna opción de línea de comandos como GNU grep para aceptar Perl-compatible regular expresiones, solo corre

pcregrep "Ui\.(?!Line)"

No necesita otro grupo anidado para "Línea" como en su ejemplo "Ui. (?! (Línea))" - el grupo externo es suficiente, como lo he mostrado arriba.

Déjame darte otro ejemplo de aseveraciones negativas: cuando tienes una lista de líneas, devuelta por "ipset", cada línea muestra el número de paquetes en el medio de la línea, y no necesitas líneas con cero paquetes, simplemente correr:

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

Si le gustan las expresiones regulares compatibles con perl y tiene perl pero no tiene pcregrep o su grep no es compatible con --perl-regexp, puede usar scripts de perl de una línea que funcionen de la misma manera que grep:

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

Perl acepta stdin de la misma manera que grep, por ejemplo

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