2
votes

Suppression de N modèles correspondants en utilisant sed, awk, perl, etc.

J'ai cette situation, le fichier contient

abcd
line1
line2
line3
vwxyz
line4
line5

Je voudrais supprimer chaque fois que je trouve 2 lignes correspondantes de

abcd
vwxyz

à être supprimé pour qu'il devienne

abcd
line1
line2
line3
vwxyz
abcd
vwxyz
abcd
vwxyz
abcd
vwxyz
line4
line5

J'ai recherché de nombreuses suggestions de stackoverflow de correspondance de modèle mais je n'ai jamais rencontré de correspondance de modèle de 2 lignes

J'ai triend sed -i '/ abcd | vwxyz / d' file1 mais cela n'a pas fonctionné comme prévu ...

Toute aide est appréciée en utilisant awk, sed, perl, pyhon etc p>


2 commentaires

Nous pensions que vous aviez essayé quelque chose ... Code?


sed -i '/ abcd | vwxyz / d' file1 mais cela n'a pas fonctionné comme prévu


6 Réponses :


2
votes

Cela pourrait fonctionner pour vous (GNU sed):

sed -z 's/^abcd\nvwxyz\n//mg' file

Créez une fenêtre de 2 lignes sur toute la longueur du fichier et supprimez cette fenêtre si la fenêtre actuelle correspond aux chaînes requises. Sinon, imprimez / supprimez la première ligne de la fenêtre et ajoutez une autre ligne, répétez.

Ceci peut être étendu pour correspondre à n lignes:

sed ':a;N;s/[^\n]\+/&/3;Ta;/^line1\nline2\nline3$/d;P;D' file

généralisé pour 2 lignes:

Ou:

sed ':a;N;s/[^\n]\+/&/2;Ta;/^abcd\nvwxyz$/d;P;D' file

pour 3 lignes, etc.

Une alternative:

sed 'N;/^abcd\nvwxyz$/d;P;D' file


5 commentaires

Merci, la première option semble assez simple ... je dois me cogner la tête pour ne pas avoir pensé à \ n :)


^ signifie "début de chaîne" et non "début de ligne" donc sed -z 's / ^ ...' ne correspondra pas seulement au texte au début de le fichier? Je n'ai aucune idée de ce que fait le m avant le g alors peut-être que c'est pour " m agic" ... :-).


@EdMorton le drapeau m qui suit la commande de substitution est pour multiligne, où ^ et $ représentent les ancres de début / fin de ligne.


@potong - ah, ok merci pour l'explication. Cette ligne de commande sort juste le fichier d'entrée inchangé quand je l'ai essayé tout à l'heure avec GNU sed 4.4-1.


@EdMorton merci Ed Je suis actuellement sous GNU sed 4.2.2 et je ne rencontre pas ce problème. Veuillez utiliser la première solution et peut-être si cette erreur persiste, envoyez un rapport de bogue au responsable de GNU sed. Je crois que la version actuelle est GNU sed 4.7.



2
votes

Vous voulez dire quelque chose comme ça?

/abcd/    # search first Pattern
{
N;        # if match read next line
/vwxyz/d  # if second pattern match then delete line
}

explication

sed '/abcd/{N;/vwxyz/d}' yourdata.file


1 commentaires

Cela pourrait ne pas supprimer une paire de lignes si une telle paire était précédée de abcd .



2
votes

Avec Perl, la solution la plus simple est de charger l'intégralité de l'entrée en mémoire.

if (!eof()) {
   my @buf = scalar(<>);
   while (<>) {
      if ($buf[-1] =~ /^abcd$/ && /^vwxyz$/) {
         @buf = ();
      } else {
         print(shift(@buf));
         push(@buf, $_);
      }
   }

   print @buf;
}

Si vous lisez une ligne à la fois, la solution générique dans ces types de problèmes est de garder un tampon des dernières lignes.

perl -0777pe's/abcd\nvwxyz\n//g'


0 commentaires

0
votes

Avec awk , où vous définissez votre séparateur d'enregistrement comme les 2 lignes et vous imprimez simplement chaque ligne.

python3 filter_lines.py 
abcd
line1
line2
line3
vwxyz
line4
line5

Une solution python: p>

import re

with open('file.in', 'r') as file:
    data = file.read()
    print(re.sub(r'(^|\n)abcd\nvwxyz(?=\n)','',data), end='')

sortie:

awk -v RS='abcd\nvwxyz\n' '{printf $0}' file.in 
abcd
line1
line2
line3
vwxyz
line4
line5


3 commentaires

Vous devez mentionner que nécessite GNU awk pour RS multi-caractères. Utilisez toujours printf "% s", $ 0 au lieu de printf $ 0 car ce dernier échouera lorsque $ 0 contient des caractères de formatage printf tels que % s . Il devrait vraiment être écrit comme awk -v RS = '(^ | \ n) abcd \ nvwxyz \ n' '1' mais pour gérer les cas où abcd et / ou vwxyz peut apparaître au milieu et / ou au début et / ou à la fin du fichier. J'ai l'impression qu'il manque encore un cas ...


Ah, ouais - le problème avec ce RS dans mon commentaire est qu'il ne correspondra pas au 2ème \ nabcd \ nvwxy \ n quand il y a 2 blocs consécutifs car il consomme le \ n aux deux extrémités de chaque bloc. Je devrais réfléchir à la vraie RS nécessaire pour cela - ce n'est peut-être tout simplement pas une option.


Après réflexion - vous ne pouvez pas faire cela de manière robuste simplement en définissant RS sur la chaîne multiligne cible avec le début / la fin de la ligne environnante, j'ai donc publié une nouvelle réponse.



1
votes

Pourriez-vous s'il vous plaît essayer ce qui suit, étant donné que votre fichier d'entrée réel est le même que les exemples illustrés.

awk -v RS="" '{gsub(/abcd\nvwxyz/,"");gsub(/[[:space:]]+\n/,ORS)} 1' Input_file


0 commentaires

1
votes

Un peu plus cryptique que je ne le souhaiterais mais avec GNU awk pour RS multi-caractères:

awk -v RS='\nbar\netc\nabcd\nvwxyz\n' -v ORS= '!sub(/(^|\n)foo$/,""){$0=$0 RT} 1' file

Malheureusement, si nous allons avec une solution basée sur RS (par opposition à garder un tampon roulant de 2 lignes ou similaire) alors c'est ainsi que cela doit être fait pour accueillir la chaîne multiligne apparaissant au début et / ou à la fin du fichier et / ou dans des blocs répétés et / ou en commençant / s'arrêtant au milieu d'autres lignes. Il peut être utilisé tel quel pour de longs blocs de lignes arbitraires:

awk -v RS='\nvwxyz\n' -v ORS= '!sub(/(^|\n)foo\nbar\netc\nabcd$/,""){$0=$0 RT} 1' file

ou si vous préférez:

$ awk -v RS='\nvwxyz\n' -v ORS= '!sub(/(^|\n)abcd$/,""){$0=$0 RT} 1' file
abcd
line1
line2
line3
vwxyz
line4
line5

p >


0 commentaires