commande unix pour obtenir des lignes entre la première et la dernière occurrence d’un mot et écrire dans un fichier

Je veux une commande Unix pour trouver les lignes entre la première et la dernière occurrence d’un mot

Par exemple:

imaginons que nous avons 1000 lignes. La dixième ligne contient le mot “stackoverflow”, la trente-cinquième ligne contient également le mot “stackoverflow”.

Je veux imprimer des lignes entre 10 et 35 et les écrire dans un nouveau fichier.

Vous pouvez le faire en deux étapes. L’idée de base est de:

1) obtenir le numéro de ligne du premier et du dernier match.

2) imprimer la plage de lignes entre ces plages.

$ read first last <<< $(grep -n stackoverflow your_file | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}') $ awk -vf=$first -vl=$last 'NR>=f && NR<=l' your_file 

Explication

  • read first last lit deux valeurs et les stocke dans $first et $last .
  • grep -n stackoverflow your_file greps et affiche la sortie comme ceci: number_of_line:output
  • awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}') awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}') affiche le numéro de la première et dernière correspondance de stackoverflow dans le fichier.

Et

  • awk -vf=$first -vl=$last 'NR>=f && NR<=l' your_file imprime toutes les lignes de $first numéro de ligne jusqu'à $last numéro de ligne.

Tester

 $ cat a here we have some text stackoverflow and other things bla bla bla bla stackoverflow and whatever else stackoverflow to make more fun blablabla $ read first last <<< $(grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}') $ awk -vf=$first -vl=$last 'NR>=f && NR<=l' a stackoverflow and other things bla bla bla bla stackoverflow and whatever else stackoverflow 

Par étapes:

 $ grep -n stackoverflow a 3:stackoverflow 9:stackoverflow 11:stackoverflow $ grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}' 3 11 $ read first last <<< $(grep -n stackoverflow a | awk -F: 'NR==1 {printf "%d ", $1}; END{print $1}') $ echo "first=$first, last=$last" first=3, last=11 

Si vous connaissez une limite supérieure du nombre de lignes pouvant exister (disons un million), vous pouvez utiliser ce simple script abusif:

 (grep -A 100000 stackoverflow | grep -B 1000000 stackoverflow) < file 

Vous pouvez append | tail -n +2 | head -n -1 | tail -n +2 | head -n -1 | tail -n +2 | head -n -1 pour supprimer également les lignes de bordure:

 (grep -A 100000 stackoverflow | grep -B 1000000 stackoverflow | tail -n +2 | head -n -1) < file 

Je ne suis pas sûr à 100% de la question de savoir si la sortie doit inclure les première et dernière lignes correspondantes, donc je suppose que oui. Mais cela peut être facilement changé si nous voulons à la place exclusive.

Cette solution pure-bash le fait en une seule étape – c.-à-d. Que le fichier (ou le canal) n’est lu qu’une seule fois:

 #!/bin/bash function midgrep { while read ln; do [ "$saveline" ] && linea[$((i++))]=$ln if [[ $ln =~ $1 ]]; then if [ "$saveline" ]; then for ((j=0; j 

Enregistrez ceci en tant que script (par exemple, midgrep.sh) et dirigez comme suit la sortie de votre choix:

 $ cat input.txt | ./midgrep.sh stackoverflow 

Cela fonctionne comme suit:

  • trouver la première ligne et le premier tampon dans le premier élément d'un tableau
  • continuer à lire les lignes jusqu'à la prochaine correspondance, en mettant en mémoire tampon le tableau au fur et à mesure
  • à chaque correspondance suivante, vider le tableau de tampons à la sortie
  • continuer la lecture du fichier à la fin. S'il n'y a plus de correspondances, le dernier tampon est simplement supprimé.

L'avantage de cette approche est que nous ne lisons que les entrées une seule fois. L'inconvénient est que nous mettons tout en mémoire tampon entre chaque match - s'il y a beaucoup de lignes entre chaque match, celles-ci sont toutes mises en mémoire tampon jusqu'à ce que nous trouvions la prochaine correspondance.

En outre, cela utilise l'opérateur d'expression rationnelle bash =~ pour conserver cette pure bash. Mais vous pouvez remplacer cela par un grep si vous êtes plus à l'aise avec cela.

En utilisant perl :

 perl -00 -lne ' chomp(my @arr = split /stackoverflow/); print join "\nstackoverflow", @arr[1 .. $#arr -1 ] ' file.txt | tee newfile.txt 

L’idée sous-jacente est d’alimenter un tableau de tout le fichier d’entrée en morceaux en utilisant la chaîne “stackoverflow” à séparer. Ensuite, nous imprimons les deuxièmes occurrences à la dernière -1 avec join “stackoverflow”.