Existe-t-il une alternative aux commutateurs -A -B -C de grep (pour imprimer quelques lignes avant et après)?

10
grep -A 2 -B 3 

imprime 2 lignes après la chaîne grep et imprime 3 lignes avant.

grep -C 3

imprime 3 lignes avant et 3 lignes après

Malheureusement, le que grepj'utilise ne prend pas en charge ces options. Existe-t-il des commandes ou des scripts alternatifs disponibles pour simuler cela? Vous utilisez des scripts sed/ awk/ perl/ shell?

Prashant Bhate
la source
+1 Je ne savais pas qu'il y avait un -Cinterrupteur.
Lazer
1
Installez grep de GNU. Plus généralement, lorsqu'une nouvelle machine Sun est arrivée ici, la toute première étape de la configuration a été ce que quelqu'un a appelé GNU > /usr/local. Les programmes GNU ont beaucoup d'extensions très utiles et sont conçus pour éviter les restrictions arbitraires (mais vous payez cher en taille et parfois en performances). De nombreux systèmes propriétaires ont des référentiels de paquets "non officiels" avec GNU et d'autres outils. Le "partenaire" ne vous en parlera pas, même lorsqu'ils sont gérés par le vendeur ...
vonbrand

Réponses:

6

Une façon modérément laide de le faire est

grep -v pattern file >file.tmp; diff -c file.tmp file

ou remplacer -cpar -C NUMpour les NUMlignes de contexte. Cela produira cependant une sortie supplémentaire. (Si vos diffsupports -u/ -U NUM, ce sera plus propre.)

Si vous diffn'avez pas -c/ -C/ -u, il y a encore des moyens de le faire, mais ils sont assez moche. D'un autre côté, un système qui diffne supporte même -cpas n'a probablement pas Perl non plus.

geekosaure
la source
C'est cool, fonctionne comme un charme, même si j'ai dû utiliser l'option -bitw avec cela pour le faire fonctionner pour les fichiers générés par Windows.
Prashant Bhate
Vous pouvez envoyer stdin à diff, et sauter le temporaire:grep -v pattern file | diff -c - file
Cascabel
5

ack ne nécessite que Perl, et comprend -A, -Bet les -Coptions qui fonctionnent comme grep de. Il utilise la syntaxe regex de Perl au lieu de grep, et la façon dont il sélectionne les fichiers à rechercher est assez différente. Vous voudrez peut-être essayer l' -foption lors de son utilisation (qui imprime les fichiers qu'il recherchera sans réellement rechercher quoi que ce soit).

Il peut être installé en tant que script unique qui ne nécessite aucun module non central. Déposez-le simplement dans votre ~/binrépertoire (ou n'importe où ailleurs sur votre PATH auquel vous avez accès en écriture) et assurez-vous qu'il est chmodexécutable.

cjm
la source
Sa boîte de production et malheureusement je n'ai pas assez de privilèges pour installer quoi que ce soit, et je ne peux pas le risquer, cependant, merci pour cette astuce je vais l'installer et essayer sur mon ordinateur portable à la maison
Prashant Bhate
@Prashant, vous n'avez pas besoin d'installer root ackpour votre propre usage.
cjm
Oui mais je ne peux toujours pas l'utiliser là-bas, bien que ce soit sûr que ce script restera pour toujours dans mon ~ / bin :)
Prashant Bhate
@Prashant: Pourquoi ne pouvez-vous pas l'utiliser? C'est juste un script Perl.
intuition
1
Sa boîte de PRODUCTION, doit prendre des autorisations spéciales, approuver bla bla bla ... pour faire quoi que ce soit dessus. et tout va mal là-dessus vient sur ma tête;) et ça ne vaut pas le coup :)
Prashant Bhate
5

Ce script perl simple émule grep -Adans une certaine mesure

#!/usr/bin/perl

$pattern=shift; #patthern to search
$lines=shift; # number of lines to print

$n = 0;
while (<>) {
  $n = $lines if /$pattern/; # reset counting
  if ($n) { print; $n-- } # print if within
  $n = 0 if eof; # don't leak across file boundaries
}

Notez que vous pouvez ajouter une déclaration d'utilisation, pour rendre le script lisible et utilisable;)

USAGE:    $./grep-A.pl <pattern> <numLines> <filename> 
Vijay Anant
la source
Bien, de quelle version de perl ai-je besoin pour exécuter ceci?
Prashant Bhate
J'utilise v5.10.1, je suppose que perl 5 est assez courant de nos jours.
Vijay Anant
ya son 5.8.8 et ça marche, super, mais j'ai besoin d'un script qui fait ce que -B
Prashant Bhate
Bien. Je changerais cependant l'ordre des arguments; grep-A 3 foosemble beaucoup plus naturel que grep-A foo 3. :-)
musiphil
3

Vous pouvez simplement installer GNU grep ou Ack (écrit en Perl, comprend de nombreuses options de GNU grep et plus).

Si vous préférez vous en tenir aux outils standard et à un peu de script, voici un script awk qui émule le comportement des options -Aet des grep GNU -B. Peu testé.

#!/bin/sh
# grep-ac: a grep-like awk script
# Arguments: pattern = awk regexp to search for
#            before = number of lines to print before a match
#            after = number of lines to print after a match
{ "exec" "awk" "-f" "$0" "$@"; }
# The array h contains the history of lines that haven't been printed
# but are eligible for being "before" lines.
# The variable until contains the number of the last "after" line to print.
match($0, pattern) {   # the current line matches
    for (i in h) {
        print h[i];    # print each remaining before line
        delete h[i];   # delete each line as it's printed
    }
    until=NR+after;    # record the last after line to print
}
{
    if (NR<=until) print $0;    # from a match to its last after line: print
    else h[NR]=$0;              # after that: save in history
    delete h[NR-before];        # remove line too old to be a before line
}
END {exit !until}               # exit status: 0 if there was a match, else 1

Exécutez-le comme grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTERPATTERNest le modèle à rechercher (une expression régulière étendue avec quelques ajouts awk ), et NBEFOREet NAFTERsont les nombres de lignes à imprimer avant et après une correspondance respectivement (par défaut à 0). Exemple:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar'
Gilles 'SO- arrête d'être méchant'
la source
Toute solution qui stocke des données dans un tableau est hors de question ... comme je l'ai mentionné plus tôt, la taille des fichiers est assez énorme et peut déborder. De plus, awk sur ce système ne permet pas une taille de fichier supérieure à 3000 octets.
Prashant Bhate
2
@Prashant: Je ne comprends pas vos objections. Ce script supprime les lignes une fois qu'elles ne sont pas éligibles pour être avant-lignes. Il n'utilise pas plus de mémoire que ce qui est intrinsèquement nécessaire compte tenu des exigences, sauf que awk peut avoir une surcharge plus élevée qu'un programme à usage spécial (mais moins que Perl, que vous envisagez également). La taille totale du fichier est totalement hors de propos.
Gilles 'SO- arrête d'être méchant'
2
{ "exec" "awk" "-f" "$0" "$@"; }: moyen très astucieux de contourner les limites de l'analyse de lignes de shebang.
dubiousjim
2

Il s'avère qu'il est assez difficile d'émuler -B, en raison des problèmes qui surviennent lorsque vous avez des lignes correspondantes qui se suivent directement. Cela interdit à peu près toute sorte d'analyse de fichiers à passage unique.

Je l'ai réalisé en jouant avec l'approximation suivante:

perl -pe 'if(/search_term/) {print foreach @A; print ">"; $B=4}; shift @A if push(@A, $_)>7; $_ = "" unless ($B-- > 0);' target_file

Cela fonctionnera à peu près correctement comme le ferait grep -A7 -B3, avec la mise en garde décrite dans le premier paragraphe.

Une solution alternative (également à fichier unique) à ce problème consiste à utiliser perl pour alimenter sed une chaîne de commande:

sed -n `perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file` file
user455
la source
oneliner assez long, mais, ce fichier est très énorme, donc pousser des lignes dans le tableau dans ce cas est une mauvaise idée, n'est-ce pas?
Prashant Bhate
Le shift @A if push(@A,$_)>7;bit ne conserve qu'un tableau de taille maximale 7 autour. (c'est votre paramètre -A). La deuxième option conserve un fichier incroyablement petit (exécutez simplement perl sans la couche externe sed pour voir ce qui y est généré), mais elle lit le fichier deux fois.
user455
0

En utilisant, sedvous pouvez d'abord obtenir les numéros de ligne des lignes correspondantes, décrémenter et incrémenter un numéro de ligne donné dans une whileboucle, puis utiliser sed -n "n1,n2p"pour imprimer les lignes du contexte de début ( n1) et de fin ( n2) (similaire à l' sedalternative suggérée par user455). De nombreux processus de lecture peuvent cependant entraîner une baisse des performances.

edpeut référencer directement les lignes précédentes et suivantes d'une ligne correspondante, mais échoue si la plage de lignes spécifiée n'existe pas; par exemple, la ligne correspondante est la ligne numéro 2, mais 5 lignes de pré-correspondance doivent être imprimées. L' utilisation , edil est donc nécessaire d'ajouter un nombre approprié de lignes (vide) au début et à la fin. (Pour les fichiers volumineux, ce edn'est peut-être pas le bon outil, voir: bfs - scanner de gros fichiers ).

# sample code to match lines with number 5 plus previous & following line
# (using Bash)
printf '%s\n' {1..20} > num.txt

# sed
sed -n '/5/=' num.txt | while read num; do
   n1=$((num - 1))
   n2=$((num + 1))
   [[ $n1 -lt 1 ]] && n1=1
   sed -n "${n1},${n2}p" num.txt
   echo --
done | sed -e '${/^--$/d;}'

# ed
cat <<-'EOF' | ed -s num.txt | sed -e $'N;N;a\\\n--' | sed -e '${/^--$/d;}'
H
0i
beginning: added line one
.
$a
end: added line one
.
,g/5/km\
'm-1,'m+1p
q
EOF
larz
la source