web-dev-qa-db-fra.com

Comment obtenir la partie du fichier après la ligne qui correspond à l'expression de grep? (premier match)

J'ai un fichier avec environ 1000 lignes. Je veux la partie de mon fichier après la ligne qui correspond à mon instruction grep.

c'est à dire.

$ cat file | grep 'TERMINATE'     // Its found on line 534

Je souhaite donc que le fichier de la ligne 535 to line 1000 soit traité ultérieurement.

Comment fait-on ça ?

142
Yugal Jindle

Ce qui suit imprimera la ligne correspondant à TERMINATE jusqu'à la fin du fichier:

sed -n -e '/TERMINATE/,$p'

Expliqué: -n désactive le comportement par défaut de sed lors de l'impression de chaque ligne après l'exécution de son script, -e a indiqué un script à sed, /TERMINATE/,$ est une sélection de plage d'adresse (ligne) signifiant la première ligne correspondant à l'expression régulière TERMINATE (comme grep) à la fin du fichier ($), et p est la commande d'impression qui affiche la ligne en cours.

Cela imprimera à partir de la ligne qui suit la ligne correspondant à TERMINATE jusqu'à la fin du fichier:
(de APRES la ligne correspondante à EOF, n'incluant pas la ligne correspondante)

sed -e '1,/TERMINATE/d'

Expliqué: 1,/TERMINATE/ est une sélection de plage d'adresse (ligne) signifiant la première ligne pour l'entrée de la 1re ligne correspondant à l'expression régulière TERMINATE, et d est la commande de suppression qui supprime la ligne actuelle et passe à la ligne suivante. Le comportement par défaut de sed étant d'imprimer les lignes, les lignes après TERMINATE seront imprimées jusqu'à la fin de l'entrée.

Modifier:

Si vous voulez les lignes avant TERMINATE:

sed -e '/TERMINATE/,$d'

Et si vous voulez les deux lignes avant et après TERMINATE dans 2 fichiers différents en un seul passage:

sed -e '1,/TERMINATE/w before
/TERMINATE/,$w after' file

Les fichiers before et after contiendront la ligne avec terminate, donc pour traiter chacun d’entre eux, vous devez utiliser:

head -n -1 before
tail -n +2 after

Edit2:

SI vous ne voulez pas coder en dur les noms de fichiers dans le script sed, vous pouvez:

before=before.txt
after=after.txt
sed -e "1,/TERMINATE/w $before
/TERMINATE/,\$w $after" file

Mais vous devez ensuite échapper à $, qui signifie la dernière ligne, pour éviter que le shell n'essaie d'élargir la variable $w (notez que nous utilisons maintenant des guillemets doubles autour du script plutôt que des guillemets simples).

J'ai oublié de dire que la nouvelle ligne est importante après les noms de fichiers dans le script afin que sed sache que les noms de fichiers se terminent.


Edit: 2016-0530

Sébastien Clément a demandé: "Comment pourriez-vous remplacer la TERMINATE codée en dur par une variable?"

Vous pouvez créer une variable pour le texte correspondant, puis procéder de la même manière que dans l'exemple précédent: 

matchtext=TERMINATE
before=before.txt
after=after.txt
sed -e "1,/$matchtext/w $before
/$matchtext/,\$w $after" file

utiliser une variable pour le texte correspondant avec les exemples précédents:

## Print the line containing the matching text, till the end of the file:
## (from the matching line to EOF, including the matching line)
matchtext=TERMINATE
sed -n -e "/$matchtext/,\$p"
## Print from the line that follows the line containing the 
## matching text, till the end of the file:
## (from AFTER the matching line to EOF, NOT including the matching line)
matchtext=TERMINATE
sed -e "1,/$matchtext/d"
## Print all the lines before the line containing the matching text:
## (from line-1 to BEFORE the matching line, NOT including the matching line)
matchtext=TERMINATE
sed -e "/$matchtext/,\$d"

Les points importants concernant le remplacement de texte par des variables dans ces cas sont les suivants: 

  1. Les variables ($variablename) incluses dans single quotes ['] ne "se développeront" pas, mais les variables à l'intérieur de double quotes ["] le seront. Donc, vous devez changer tous les single quotes en double quotes s’ils contiennent du texte que vous voulez remplacer par une variable. 
  2. Les plages sed contiennent également un $ et sont immédiatement suivies d'une lettre du type: $p, $d, $w. Elles ressembleront également à des variables à développer. Vous devez donc échapper ces caractères $ avec une barre oblique inversée [\] telle que: \$p, \$d, \$w.
263
jfg956

Comme une approximation simple, vous pouvez utiliser

grep -A100000 TERMINATE file

qui greps pour TERMINATE et génère jusqu'à 100000 lignes suivant cette ligne.

De la page de manuel

-A NUM, --after-context=NUM

Affiche NUM lignes du contexte de fin après les lignes correspondantes. Place une ligne contenant un séparateur de groupe (-) entre groupes de matches contigus. Avec le -o ou --only-matching- option, cela n'a aucun effet et un avertissement est donné.

59
aioobe

Un outil à utiliser ici est awk:

cat file | awk 'BEGIN{ found=0} /TERMINATE/{found=1}  {if (found) print }'

Comment cela marche-t-il:

  1. Nous mettons à zéro la variable 'trouvée', en évaluant false
  2. si une correspondance pour 'TERMINATE' est trouvée avec l'expression régulière, nous la définissons à un.
  3. Si notre variable 'trouvé' est évaluée à True, imprimez :)

Les autres solutions risquent de consommer beaucoup de mémoire si vous les utilisez sur des fichiers très volumineux.

25
Jos De Graeve

Utilisez le développement des paramètres bash comme suit:

content=$(cat file)
echo "${content#*TERMINATE}"
7
Mu Qiao

Si je comprends bien votre question, vous souhaitez que les lignes aprèsTERMINATE, sans la TERMINATE- line. awk peut le faire de manière simple:

awk '{if(found) print} /TERMINATE/{found=1}' your_file

Explication:

  1. Bien que ce ne soit pas une bonne pratique, vous pouvez vous fier au fait que tous les vars ont la valeur par défaut 0 ou la chaîne vide si elle n’est pas définie. Ainsi, la première expression (if(found) print) n’imprimera rien pour commencer.
  2. Une fois l’impression terminée, nous vérifions s’il s’agit bien de la ligne de départ (à ne pas inclure).

Ceci imprimera toutes les lignes après la ligne TERMINATE-.


Généralisation:

  • Vous avez un fichier avec les lignes start et end et vous souhaitez que les lignes entre ces lignes excluent les lignes start - et end.
  • Les lignes start - et end peuvent être définies par une expression régulière correspondant à la ligne.

Exemple:

$ cat ex_file.txt 
not this line
second line
START
A good line to include
And this line
Yep
END
Nope more
...
never ever
$ awk '/END/{found=0} {if(found) print} /START/{found=1}' ex_file.txt 
A good line to include
And this line
Yep
$

Explication:

  1. Si la ligne end est trouvée, aucune impression ne doit être effectuée. Notez que cette vérification est effectuée avant l’impression en cours pour exclure la ligne end du résultat.
  2. Imprimer la ligne en cours si found est défini.
  3. Si la ligne start est trouvée, définissez found=1 pour que les lignes suivantes soient imprimées. Notez que cette vérification est effectuée après l'impression réelle pour exclure la ligne start du résultat.

Remarques:

  • Le code repose sur le fait que la valeur par défaut de awk-vars est 0 ou la chaîne vide si elle n'est pas définie. Ceci est valide mais peut ne pas être la meilleure pratique afin que vous puissiez ajouter un BEGIN{found=0} au début de l'expression awk.
  • Si plusieurs blocs start-end sont trouvés, ils sont tous imprimés.
7
UlfR

Si, pour une raison quelconque, vous souhaitez éviter d'utiliser sed, les opérations suivantes imprimeront la ligne correspondant à TERMINATE jusqu'à la fin du fichier:

tail -n "+$(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)" file

et ce qui suit sera imprimé à partir de la ligne suivante correspondant à TERMINATE jusqu'à la fin du fichier:

tail -n "+$(($(grep -n 'TERMINATE' file | head -n 1 | cut -d ":" -f 1)+1))" file

Il faut 2 processus pour faire ce que sed peut faire en un seul processus, et si le fichier change entre l'exécution de grep et de tail, le résultat peut être incohérent. Je recommande donc d'utiliser sed. De plus, si le fichier ne contient pas TERMINATE, la 1ère commande échoue.

3
jfg956

Il y a plusieurs façons de le faire avec sed ou awk:

sed -n '/TERMINATE/,$p' file

Ceci recherche TERMINATE dans votre fichier et imprime à partir de cette ligne jusqu'à la fin du fichier.

awk '/TERMINATE/,0' file

C'est exactement le même comportement que sed.

Si vous connaissez le numéro de la ligne à partir de laquelle vous souhaitez imprimer, vous pouvez le spécifier avec NR (numéro de l'enregistrement, qui indique le numéro de la ligne):

awk 'NR>=535' file

Exemple

$ seq 10 > a        #generate a file with one number per line, from 1 to 10
$ sed -n '/7/,$p' a
7
8
9
10
$ awk '/7/,0' a
7
8
9
10
$ awk 'NR>=7' a
7
8
9
10
2
fedorqui

grep -A 10000000 Fichier 'TERMINATE' 

  • c’est beaucoup, beaucoup plus rapide que sed, en particulier sur de très gros fichiers. Cela fonctionne jusqu'à 10 millions de lignes (ou tout ce que vous avez inséré), donc pas de mal à rendre cela assez grand pour gérer tout ce que vous frappez.
1
user8910163

Cela pourrait être une façon de le faire. Si vous savez quelle ligne du fichier vous avez votre grep Word et combien de lignes vous avez dans votre fichier:

grep -A466 Fichier 'TERMINATE'

0
Mariah

Alternatives à l'excellente réponse sed de jfgagne et qui n'incluent pas la ligne correspondante:

0
mivk