Trouver toutes les occurrences dans un fichier avec sed

15

Utilisation du système d'exploitation OPEN STEP 4.2 ... J'utilise actuellement la sedcommande suivante :

sed -n '1,/141.299.99.1/p' TESTFILE | tail -3

Cette commande trouvera une instance dans un fichier avec l'IP de 141.299.99.1 et inclura également 3 lignes avant ce qui est tout bon, à l'exception que je voudrais également trouver toutes les instances de l'IP et les 3 lignes avant et pas seulement le premier.

Vallée
la source
1
Veuillez toujours inclure votre système d'exploitation. Les solutions dépendent très souvent du système d'exploitation utilisé. Utilisez-vous Unix, Linux, BSD, OSX, autre chose? Quelle version?
terdon
GRAND POINT! L'utilisation d'Open Step version 4.2 est assez ancienne et les shells inclus ne comprennent pas la plupart des fonctionnalités mentionnées dans les réponses ci-dessous.
Dale
Par curiosité - qu'est-ce qu'un système OPEN STEP 4.2 et à quoi sert-il aujourd'hui?
Thorbjørn Ravn Andersen
(et si Perl est disponible, vous pouvez vraiment faire beaucoup de belles choses juste avec ça)
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Peut-être que c'est ça: en.wikipedia.org/wiki/OpenStep
Barmar

Réponses:

4

Voici une tentative d'émulation à l' grep -B3aide d'une fenêtre mobile sed, basée sur cet exemple GNU sed (mais avec un peu de chance, conforme POSIX - avec remerciement à @ StéphaneChazelas):

sed -e '1h;2,4{;H;g;}' -e '1,3d' -e '/141\.299\.99\.1/P' -e '$!N;D' file

Les deux premières expressions amorcent un tampon de modèle multi-lignes et lui permettent de gérer le cas de bord dans lequel il y a moins de 3 lignes de contexte précédent avant la première correspondance. L'expression du milieu (correspondance d'expression régulière) imprime une ligne en haut de la fenêtre jusqu'à ce que le texte de correspondance souhaité ait ondulé à travers le tampon de modèle. La finale fait $!N;Ddéfiler la fenêtre d'une ligne sauf lorsqu'elle atteint la fin de la saisie.

tournevis
la source
-en'est pas spécifique à GNU. Pour être POSIX / portable, vous en avez besoin car il ne peut rien y avoir après }(et vous en avez besoin ;avant).
Stéphane Chazelas
Merci @ StéphaneChazelas - alors dites-vous que pour être POSIX / portable, le premier groupe doit être divisé / modifié comme -e '1h;2,4{H;g;}' -e '1,3d'? Je n'ai pas de système non GNU sur lequel tester (et le --posixcommutateur GNU sed ne semble pas s'en soucier).
steeldriver
1
Oui, sous Linux, vous pouvez tester une implémentation différente avec le seddu heirloom toolchest qui est un descendant du sed Unix traditionnel. La spécification POSIX / Unix pour sedest sur pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
Stéphane Chazelas
Je reçois un événement introuvable sur l'un ou l'autre: N; D ': événement introuvable. Suis-je manquant de syntaxe quelque part? Merci!!
Dale
Désolé, je viens de réaliser que ma dernière modification a omis un guillemet de fermeture après la première expression -e. Je l'ai corrigé maintenant - pouvez-vous réessayer avec l'expression ci-dessus s'il vous plaît?
steeldriver
10

grep fera un meilleur travail de ceci:

grep -B 3 141.299.99.1 TESTFILE

Le -B 3moyen d'imprimer les trois lignes avant chaque match. Cela s'imprimera --entre chaque groupe de lignes. Pour désactiver cela, utilisez --no-group-separatorégalement.

L' -Boption est également prise en charge par GNUgrep et la plupart des versions BSD ( OSX , FreeBSD , OpenBSD , NetBSD ), mais ce n'est techniquement pas une option standard.

Michael Homer
la source
1
Michael Homer - Merci. Je n'ai pas l'option -B. D'autres idées?
Dale
@Dale Pouvez-vous installer GNU grep? Cela vous donnera l'option.
Barmar
9

Avec sedvous pouvez faire une fenêtre coulissante.

sed '1N;$!N;/141.299.99.1/P;D'

Ça le fait. Mais méfiez-vous bashdu comportement fou de l'expansion ! même lorsqu'il est cité !!! dans la chaîne de commande de votre historique de commande pourrait le rendre un peu fou. Préfixez la commande avec set +H;si vous trouvez que c'est le cas. Pour ensuite le réactiver (mais pourquoi ???), faites-le set -Hensuite.

Cela, bien sûr, ne s'appliquerait si vous étiez utilisez bash- bien que je ne crois pas que vous êtes. Je suis assez certain que vous travaillez avec csh- (qui se trouve être le shell dont le comportement insensé bashémule avec l'expansion de l'historique, mais peut-être pas aux extrêmes que le shell c a pris) . Donc , probablement un \!devrait fonctionner. J'espère.

Tout est du code portable: POSIX décrit ainsi ses trois opérateurs: (même s'il convient de noter que je n'ai confirmé que cette description existait déjà en 2001)

[2addr]N Ajoutez la ligne d'entrée suivante, moins sa ligne terminale \n, à l'espace de motif, en utilisant une ligne intégrée \npour séparer le matériau ajouté du matériau d'origine. Notez que le numéro de ligne actuel change.

[2addr]P Écrivez l'espace de motif, jusqu'à la première ligne \nélectronique, sur la sortie standard.

[2addr]D Supprimez le segment initial de l'espace de motif via la première ligne \nélectronique et démarrez le cycle suivant.

Donc, sur la première ligne, vous ajoutez une ligne supplémentaire à l'espace de motif, cela ressemble à ceci:

^line 1s contents\nline 2s contents$

Ensuite, sur la première ligne et chaque ligne suivante - à l'exception de la toute dernière - vous ajoutez une autre ligne à l'espace de motif. Il ressemble donc à ceci:

^line 1\nline 2\nline 3$

Si votre adresse IP se trouve en vous, Pimprimez jusqu'à la première nouvelle ligne, alors juste la ligne 1 ici. À la fin de chaque cycle, vous les Dsupprimez et recommencez avec ce qui reste. Le prochain cycle ressemble donc à:

^line 2\nline 3\nline 4$

...etc. Si votre adresse IP se trouve sur l'un de ces trois, le plus ancien s'imprimera - à chaque fois. Vous n'avez donc que trois lignes d'avance.

Voici un petit exemple. Je vais faire imprimer un tampon de trois lignes pour chaque numéro se terminant par zéro:

seq 10 52 | sed '1N;$!N;/0\(\n\|$\)/P;D'

10
18
19
20
28
29
30
38
39
40
48
49
50

Celui-ci est un peu plus compliqué que votre cas, car j'ai dû alterner entre la 0\nnouvelle ligne ou la 0$fin de l'espace de motif pour ressembler davantage à votre problème - mais ils sont subtilement différents en ce sens que cela nécessite une ancre - ce qui peut être un peu difficile à faire car l'espace-modèle change constamment.

J'ai utilisé les cas étranges de 10 et 52 pour montrer que tant que l'ancre est flexible, la sortie l'est également. De manière totalement portable, je peux obtenir les mêmes résultats en comptant plutôt sur l'algorithme et en faisant:

seq 10 52 | sed '1N;$!N;/[90]\n/P;D'

Et élargissez la recherche tout en restreignant ma fenêtre - de 0 à 9 et 0 et de 3 lignes à deux.

Quoi qu'il en soit, vous avez l'idée.

mikeserv
la source
Merci pour tout votre travail acharné. Désolé, où dois-je mettre le nom du fichier dans lequel je voudrais qu'il recherche?
Dale
@Dale - ma mauvaise. sed '...' $filename. Soit dit en passant - j'ai laissé dans les périodes de votre propre chaîne de recherche, mais ce ne sont pas en fait des périodes dans un modèle - celles-ci représentent n'importe quel caractère. Vous devriez probablement faire oct\.oct\.oct\.octpour leur échapper afin qu'ils ne correspondent qu'à des périodes.
mikeserv
J'ai essayé de le catcher avec différents symboles <> et je ne trouve pas d'événement que j'obtiens avec d'autres solutions ici, donc je me demande si mon système d'exploitation n'est pas compatible avec ces solutions.
Dale
résulte maintenant avec -> N; /141.299.99.1/P; D ': événement introuvable.
Dale
@Dale - veuillez consulter la mise à jour. Cela devrait vous aider.
mikeserv
4

Puisque vous mentionnez que vous n'en avez pas la -Bpossibilité grep, vous pouvez utiliser Perl (par exemple) pour faire glisser une fenêtre de 4 lignes:

perl -ne '
    push @window,$_;
    shift @window if @window > 4;
    print @window if /141\.299\.99\.1/
' your_file

La réponse de Ramesh fait la même chose avec awk.

Joseph R.
la source
Je ne sais pas si ma version de Perl prend en charge cela, mais je vais essayer. Merci beaucoup d'avoir pris le temps de répondre à ma question - très reconnaissant!
Dale
@Dale Vous êtes les bienvenus. Je doute que ce code utilise des fonctionnalités Perl de pointe.
Joseph R.
4

Lorsque disponible, vous pouvez utiliser pcregrep :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file
le chaos
la source
Vérifier si j'ai PCREGREP. J'aime la compacité de la commande. Très reconnaissant pour votre temps et vos efforts. Je vous remercie!!!
Dale
4

Vous pouvez implémenter la même approche de base que les autres réponses non grep dans le shell lui-même (cela suppose un shell relativement récent qui prend en charge =~):

while IFS= read -r line; do 
    [[ $line =~ 141.299.99.1 ]] && printf "%s\n%s\n%s\n%s\n" $a $b $c $line;
    a=$b; b=$c; c=$line; 
done < file 

Alternativement, vous pouvez extraire le fichier entier dans un tableau:

perl -e '@F=<>; 
        for($i=0;$i<=$#F;$i++){
          print $F[$i-3],$F[$i-2],$F[$i-1],$F[$i] if $F[$i]=~/141.299.99.1/
        }' file 
terdon
la source
Ma coquille est très ancienne - Steve Jobs Open Step. Excellente idée cependant et merci pour votre temps !!! Dale
Dale
@Dale l'approche perl fonctionnera à peu près n'importe où. Veuillez nous indiquer votre système d'exploitation (ajoutez-le à votre question) afin que nous puissions suggérer des choses qui fonctionneront pour vous.
terdon
Si je copie votre Perl et le mets dans le Bloc-notes et le mets sur une seule ligne ça marche! Question - si je voulais, disons 10 lignes avant le modèle de correspondance, où changerais-je les 3 en 10? Merci!
Dale
Je vois que je peux ajouter plus de lignes en ajoutant plus de $ F [$ iX], instructions. Merci!
Dale
4

Si votre système ne prend pas en charge le grepcontexte, vous pouvez essayer ack-grep à la place:

ack -B 3 141.299.99.1 file

ack est un outil comme grep, optimisé pour les programmeurs.

cuonglm
la source
J'aime la compacité de la commande, mais mon système ne prend pas en charge ack dans les pages de manuel. Excellente idée et merci beaucoup pour votre temps !!! Dale
Dale
@Dale: surprenant! Quel est votre OS? Si c'est le cas perl, vous pouvez utiliser ack.
cuonglm
2
awk '/141.299.99.1/{for(i=1;i<=x;)print a[i++];print} {for(i=1;i<x;i++)
     a[i]=a[i+1];a[x]=$0;}'  x=3 filename

Dans cette awksolution, un tableau est utilisé qui contiendra toujours 3 lignes avant le motif actuel. Par conséquent, lorsque le motif est mis en correspondance, le contenu du tableau avec le motif actuel est imprimé.

Essai

-bash-3.2$ cat filename
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.5
10.0.0.6
10.0.0.7
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
10.0.0.17
10.0.0.18
10.0.0.19

Après avoir exécuté la commande, la sortie est,

10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
Ramesh
la source
si détaillé - merci beaucoup. Je vais essayer. Très reconnaissant pour votre temps !! Dale
Dale
J'ai un fichier de test et votre solution fonctionne! Le problème est que lorsque je l'exécute sur mon gros fichier de production, il revient avec un numéro d'enregistrement trop long, de sorte que la sortie ne peut pas fonctionner avec la commande. Ma commande d'origine en haut de cette page fonctionne mais ne trouve qu'une seule instance. J'apprécie ton aide. Y a-t-il quelque chose que je puisse faire avec ma commande d'origine pour lui faire trouver plus d'une instatnce?
Dale
1

Dans la plupart d'entre eux, /141.299.99.1/correspondra également (par exemple) 141a299q99+1ou 141029969951parce que .dans une expression régulière peut représenter n'importe quel caractère.

L' utilisation /141[.]299[.]99[.]1/est plus sûre et vous pouvez ajouter un contexte supplémentaire au début et à la fin de l'ensemble regexp pour vous assurer qu'il ne correspond pas 3141., .12, .104, etc.

user117529
la source
1
C'est un bon point - et je l'ai également considéré. Pourtant, j'ai utilisé la chaîne fournie par le demandeur comme une correspondance de travail connue - et je l'ai informé personnellement de la même lorsque l'occasion lui a été fournie. Quoi qu'il en soit - pas tous - la réponse de Steeldriver a cité le match de caractères depuis le début.
mikeserv