Limite el contexto grep a N caracteres en línea


31

Tengo que buscar algunos archivos JSON en los que la longitud de la línea excede algunos miles de caracteres. ¿Cómo puedo limitar grep para mostrar el contexto hasta N caracteres a la izquierda y a la derecha del partido? Cualquier herramienta que no sea grep también estaría bien, siempre que esté disponible en los paquetes comunes de Linux.

Este sería un ejemplo de salida, para el interruptor grep imaginario Ф :

$ grep -r foo *
hello.txt: Once upon a time a big foo came out of the woods.

$ grep -Ф 10 -r foo *
hello.txt: ime a big foo came of t



3
No es un duplicado Esto se trata de ± caracteres, pero su alternativa sugerida es sobre ± líneas. (Sin embargo, su referencia al stackoverflow es buena.)
roaima

Respuestas:


22

Con GNU grep:

N=10; grep -roP ".{0,$N}foo.{0,$N}" .

Explicación:

  • -o => Imprime solo lo que hiciste coincidir
  • -P => Usar expresiones regulares de estilo Perl
  • La expresión regular dice que coincida con 0 a los $Ncaracteres fooseguido de 0 seguido por los $Ncaracteres.

Si no tienes GNU grep:

find . -type f -exec \
    perl -nle '
        BEGIN{$N=10}
        print if s/^.*?(.{0,$N}foo.{0,$N}).*?$/$ARGV:$1/
    ' {} \;

Explicación:

Como ya no podemos confiar en grepser GNU grep, utilizamos findpara buscar archivos de forma recursiva (la -racción de GNU grep). Para cada archivo encontrado, ejecutamos el fragmento de Perl.

Interruptores Perl:

  • -n Lee el archivo línea por línea
  • -l Elimine la nueva línea al final de cada línea y vuelva a colocarla al imprimir
  • -e Trate la siguiente cadena como código

El fragmento de Perl está haciendo esencialmente lo mismo que grep. Comienza configurando una variable $Nal número de caracteres de contexto que desea. Esto BEGIN{}significa que esto se ejecuta solo una vez al comienzo de la ejecución, no una vez por cada línea en cada archivo.

La instrucción ejecutada para cada línea es imprimir la línea si la sustitución de expresiones regulares funciona.

La expresión regular:

  • Haga coincidir cualquier cosa antigua perezosamente 1 al comienzo de la línea ( ^.*?) seguido de .{0,$N}como en el grepcaso, fooseguido de otro seguido .{0,$N}y finalmente haga coincidir cualquier cosa vieja perezosamente hasta el final de la línea ( .*?$).
  • Sustituimos esto con $ARGV:$1. $ARGVes una variable mágica que contiene el nombre del archivo actual que se está leyendo. $1es lo que emparejaron los padres: el contexto en este caso.
  • Las coincidencias perezosas en cualquier extremo son necesarias porque una coincidencia codiciosa se comería a todos los personajes antes foosin fallar (ya que .{0,$N}se permite que coincidan cero veces).

1 Es decir, prefiera no hacer coincidir nada a menos que esto haga que falle la coincidencia general. En resumen, combine la menor cantidad de caracteres posible.


Muy bonito, gracias. Esto tiene el inconveniente de resaltar toda la salida, no solo el texto buscado, sino que se puede solucionar agregando | grep fooal final (sin embargo, perdiendo el resaltado del nombre de archivo en el proceso).
dotancohen

1
@dotancohen, supongo que no puedes ganarlos a todos :)
Joseph R.

w / GNU greppuede especificar colores / aplicaciones coincidentes basados ​​en indicadores aplicados a través de variables de entorno. así que tal vez incluso podrías ganarlos a todos, (sin promesas, ni siquiera estoy seguro de que funcionaría en este caso) pero personalmente no veo la relevancia aquí ... de todos modos ... sigue jugando.
mikeserv

Buena respuesta. Solo una nota, usando zshNo puedo hacer que funcione pasando N = 10 como en el ejemplo. Sin embargo, funciona si export N=10antes de ejecutar el comando. ¿Alguna idea de cómo ajustar el ejemplo para trabajar con zsh?
Gabe Kopley

Operl -lne 'print "$ARGV: $_" for /.{0,10}foo.{0,10}/g'
Stéphane Chazelas el

20

Intenta usar este:

grep -r -E -o ".{0,10}wantedText.{0,10}" *

-E dice que quieres usar expresiones regulares extendidas

-o dice que solo quieres imprimir la coincidencia

-r grep busca resultados de forma recursiva en la carpeta

REGEX:

{0,10} indica cuántos caracteres arbitrarios desea imprimir

. representa un carácter arbitrario (un personaje en sí no era importante aquí, solo su número)

Editar: Ah, ya veo, que Joseph recomienda casi la misma solución que yo: D


Gracias. Aunque es esencialmente la misma solución, es inspirador de confianza que este sea el mejor método cuando dos personas lo recomiendan de forma independiente.
dotancohen

De nada, la comunidad de Unix simplemente debe cooperar, eso es lo que somos :-)
Eenoku

2
Aunque son similares, la respuesta aceptada no funcionó para mí (todavía produjo largas colas), pero esta sí. El truco con N = 10 no funciona con un bash shell.
meesern

en cygwin -E es significativamente más rápido que -P.
Bob Stein

2

Tomado de: http://www.topbug.net/blog/2016/08/18/truncate-long-matching-lines-of-grep-a-solution-that-preserves-color/ y https: // stackoverflow. com / a / 39029954/1150462

El enfoque sugerido ".{0,10}<original pattern>.{0,10}" es perfectamente bueno, excepto que el color de resaltado a menudo está desordenado. He creado un script con una salida similar, pero el color también se conserva:

#!/bin/bash

# Usage:
#   grepl PATTERN [FILE]

# how many characters around the searching keyword should be shown?
context_length=10

# What is the length of the control character for the color before and after the matching string?
# This is mostly determined by the environmental variable GREP_COLORS.
control_length_before=$(($(echo a | grep --color=always a | cut -d a -f '1' | wc -c)-1))
control_length_after=$(($(echo a | grep --color=always a | cut -d a -f '2' | wc -c)-1))

grep -E --color=always "$1" $2 | grep --color=none -oE ".{0,$(($control_length_before + $context_length))}$1.{0,$(($control_length_after + $context_length))}"

Suponiendo que el script se guarda como grepl, grepl pattern file_with_long_linesdebería mostrar las líneas coincidentes pero con solo 10 caracteres alrededor de la cadena coincidente.


0

Tubería estándar cutcon la -bbandera; Puede indicar la salida de grep a solo bytes 1 a 400 por línea.

grep "foobar" * | cut -b 1-400
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.