Limitez le contexte grep à N caractères en ligne


31

Je dois parcourir certains fichiers JSON dans lesquels la longueur des lignes dépasse quelques milliers de caractères. Comment puis-je limiter grep pour afficher le contexte jusqu'à N caractères à gauche et à droite de la correspondance? Tout outil autre que grep conviendrait également, tant qu'il est disponible dans les packages Linux courants.

Ce serait un exemple de sortie, pour le commutateur grep imaginaire Ф :

$ 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
Pas un doublon. Il s'agit d'environ ± caractères, mais votre alternative suggérée est d'environ ± lignes. (Votre référence à stackoverflow est bonne, cependant.)
roaima

Réponses:


22

Avec GNU grep:

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

Explication:

  • -o => N'imprimer que ce que vous avez assorti
  • -P => Utiliser des expressions régulières de style Perl
  • Le regex dit match 0 aux $Ncaractères foosuivi de 0 par les $Ncaractères.

Si vous n'avez pas GNU grep:

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

Explication:

Comme nous ne pouvons plus compter sur grepGNU grep, nous utilisons findpour rechercher des fichiers de manière récursive (l' -raction de GNU grep). Pour chaque fichier trouvé, nous exécutons l'extrait de code Perl.

Commutateurs Perl:

  • -n Lire le fichier ligne par ligne
  • -l Retirez la nouvelle ligne à la fin de chaque ligne et remettez-la lors de l'impression
  • -e Traitez la chaîne suivante comme du code

L'extrait de code Perl fait essentiellement la même chose que grep. Il commence par définir une variable $Nsur le nombre de caractères de contexte que vous souhaitez. Les BEGIN{}moyens ceci est exécuté qu'une seule fois au début de l' exécution pas une seule fois pour chaque ligne dans chaque fichier.

L'instruction exécutée pour chaque ligne consiste à imprimer la ligne si la substitution d'expression régulière fonctionne.

Le regex:

  • Faites correspondre n'importe quelle vieille chose paresseusement 1 au début de la ligne ( ^.*?) suivi par .{0,$N}comme dans le grepcas, suivi par foosuivi d'une autre .{0,$N}et finalement faites correspondre n'importe quelle vieille chose paresseusement jusqu'à la fin de la ligne ( .*?$).
  • Nous remplaçons cela par $ARGV:$1. $ARGVest une variable magique qui contient le nom du fichier en cours de lecture. $1est ce que les parens correspondaient: le contexte dans ce cas.
  • Les correspondances paresseuses à chaque extrémité sont requises car une correspondance gourmande mangerait tous les caractères avant foosans échouer (car elle .{0,$N}est autorisée à correspondre à zéro fois).

1 Autrement dit, préférez ne rien faire correspondre sauf si cela entraînerait l'échec de la correspondance globale. En bref, faites correspondre le moins de caractères possible.


Très bien merci. Cela a l'inconvénient de mettre en surbrillance la sortie entière, pas seulement le texte recherché, mais cela peut être contourné en ajoutant | grep fooà la fin (mais en perdant la mise en surbrillance du nom de fichier dans le processus).
dotancohen

1
@dotancohen Je suppose que vous ne pouvez pas tous les gagner :)
Joseph R.

w / GNU, grepvous pouvez spécifier les couleurs / applications de correspondance en fonction des indicateurs appliqués via les variables d'environnement. alors peut-être même que vous pourriez les gagner tous (pas de promesses - même pas sûr que cela fonctionnerait dans ce cas) mais je ne vois pas personnellement la pertinence ici ... de toute façon ... continuez à jouer.
mikeserv

Bonne réponse. Juste une note, en utilisant zshJe ne peux pas le faire fonctionner en passant N = 10 comme dans l'exemple. Cependant, cela fonctionne si je export N=10avant d'exécuter la commande. Une idée comment ajuster l'exemple pour travailler avec zsh?
Gabe Kopley

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

20

Essayez d'utiliser celui-ci:

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

-E indique que vous souhaitez utiliser l'expression régulière étendue

-o indique que vous ne souhaitez imprimer que la correspondance

-r grep recherche le résultat récursivement dans le dossier

REGEX:

{0,10} indique le nombre de caractères arbitraires que vous souhaitez imprimer

. représente un caractère arbitraire (un caractère lui-même n'était pas important ici, juste leur nombre)

Edit: Oh, je vois, que Joseph recommande presque la même solution que moi: D


Merci. Même s'il s'agit essentiellement de la même solution, il est encourageant de constater que c'est la meilleure méthode lorsque deux personnes la recommandent indépendamment.
dotancohen

Vous êtes les bienvenus, la communauté Unix doit simplement coopérer, c'est ce que nous sommes :-)
Eenoku

2
Bien qu'ils soient similaires, la réponse acceptée n'a pas fonctionné pour moi (toujours produit de longues lignes), mais celle-ci a fonctionné. L'astuce avec N = 10 ne fonctionne pas avec un shell bash.
meesern

dans cygwin -E est beaucoup plus rapide que -P.
Bob Stein

2

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

L'approche suggérée ".{0,10}<original pattern>.{0,10}"est parfaitement bonne, sauf que la couleur de surbrillance est souvent gâchée. J'ai créé un script avec une sortie similaire mais la couleur est également préservée:

#!/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))}"

En supposant que le script est enregistré sous grepl, il grepl pattern file_with_long_linesdevrait afficher les lignes correspondantes, mais avec seulement 10 caractères autour de la chaîne correspondante.


0

Passe-partout cutavec le -bdrapeau; vous pouvez indiquer à la sortie de grep uniquement les octets 1 à 400 par ligne.

grep "foobar" * | cut -b 1-400
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.