2
votes

Imprimer toutes les lignes entre deux motifs, exclusifs, première instance uniquement (en sed, AWK ou Perl)

En utilisant sed, AWK (ou Perl), comment imprimer toutes les lignes entre (la première instance de) deux motifs, à l'exclusion des motifs? 1

Autrement dit, donnée en entrée :

bbb
ccc
ddd

Ou peut-être même:

aaa
PATTERN1
bbb
ccc
ddd
PATTERN2
eee
fff
PATTERN1
ggg
hhh
iii
PATTERN2
jjj

Je m'attendrais, dans les deux cas:

aaa
PATTERN1
bbb
ccc
ddd
PATTERN2
eee

1 Un certain nombre d'utilisateurs ont voté pour fermer cette question comme un doublon de celui-ci . En fin de compte, j'ai fourni un l'essentiel qui prouve qu'ils sont différents. La question est également superficiellement similaire à a numéro sur autres , mais il n'y a pas de correspondance exacte et aucun d'entre eux n'est de haute qualité, et, comme je pense que ce problème spécifique est le plus fréquemment rencontré, il mérite une formulation claire et un ensemble de réponses correctes et claires.


3 commentaires

Meta à ce sujet: Puis-je créer cette nouvelle question ou sera-t-elle fermée en tant que dupe ou provoquera une controverse? . Assez étrange, il n'est pas marqué comme doublon de Comment sélectionner des lignes entre deux motifs? . Comme mentionné dans celui-ci, l'idée était de compiler un ensemble d'options, et pour cela, il a été marqué comme CW. Vous dites que ce n'est pas dupe parce qu'une réponse ne couvre pas un cas. Écrire encore un autre canonique me semble une perte de temps et contribue à la dispersion des connaissances.


Mmm @hek J'ai laissé mon commentaire ici, puis un débat intéressant a été reporté avec Alex, tripleee et moi. Je voudrais simplement le laisser ouvert maintenant et voir s'il attire des vues. Dans tous les cas, je vois que nous parlons de ce sujet de manière asynchrone et à différents endroits (également Meta), il est donc difficile d'arriver à un consensus.


@fedorqui J'ai suivi la discussion ici et là. Pour moi, c'est une copie claire de votre question - c'était ma première pensée, sans être influencée par le méta-post que vous avez lié ci-dessus. Je ne vois aucune bonne raison pour laquelle le PO ne devrait pas accepter cela.


6 Réponses :


5
votes

Si vous avez GNU sed (testé avec la version 4.7 sous Mac OS X), la solution la plus simple pourrait être:

sed -n '/PATTERN1/,/PATTERN2/{//!p}'

Explication:

  • La commande d supprime de la ligne 1 à la ligne correspondant à / PATTERN1 / inclus.
  • La commande Q se termine alors sans imprimer sur la première ligne correspondant à / PATTERN2 / .

Si le fichier n'a qu'une seule instance du modèle, ou si cela ne vous dérange pas de les extraire toutes, et que vous voulez une solution qui ne dépend pas d'une extension GNU, cela fonctionne: p>

sed '0,/PATTERN1/d;/PATTERN2/Q'

Explication:

  • Notez que l'expression régulière vide // répète la dernière correspondance d'expression régulière.


2 commentaires

notez que cela n'imprimerait que la première séquence de lignes de ce type entre les motifs, si telle était votre intention, veuillez ajouter cette information à la question et la question en double marquée ne tiendra plus


Désolé @Sundeep, je crois que je l'ai déjà dit, mais je l'ai maintenant rendu encore plus clair.



3
votes

Avec awk (suppose que PATTERN1 et PATTERN2 sont toujours présents par paires et qu'aucun d'eux ne se produit à l'intérieur d'une paire)

$ awk -v b=1 '/PATTERN2/ && c==b{exit} c==b; /PATTERN1/{c++}' ip.txt
bbb
ccc
ddd
$ awk -v b=2 '/PATTERN2/ && c==b{exit} c==b; /PATTERN1/{c++}' ip.txt
2
46
  • / PATTERN1 / {f = 1} définir l'indicateur si / PATTERN1 / correspond à
  • / PATTERN2 / {exit} quitter si / PATTERN2 / correspond
  • f; imprimer la ligne d'entrée si l'indicateur est défini


Solution générique, où le bloc requis peut être spécifié

$ cat ip.txt
aaa
PATTERN1
bbb
ccc
ddd
PATTERN2
eee
fff
PATTERN1
ggg
hhh
iii
PATTERN2
jjj

$ awk '/PATTERN2/{exit} f; /PATTERN1/{f=1}' ip.txt
bbb
ccc
ddd


1 commentaires

Il a été proposé de discuter pour utiliser awk '/ PATTERN1 / {f = 1; next} / PATTERN2 / {exit} f' qui, je remarque, est essentiellement le même que awk '/ PATTERN2 / {sortie} f; / PATTERN1 / {f = 1} ', c'est pourquoi je ne l'ajouterai pas comme réponse séparée.



2
votes

Cela pourrait fonctionner pour vous (GNU sed);

sed -n '/PATTERN1/{:a;n;/PATTERN2/q;p;$!ba}' file

Ceci n'affiche que les lignes entre le premier ensemble de délimiteurs, ou si le deuxième délimiteur n'existe pas, jusqu'à la fin du fichier.


0 commentaires

2
votes

J'ai tenté deux fois de répondre, mais les questions ont changé de statut de mise en attente / de duplication.

Emprunt de données à @Sundeep et ajout de la réponse que j'ai partagée dans les commentaires de la question.

Utilisation de awk p>

perl -0777 -ne ' while( /PATTERN1.*?\n(.+?)^[^\n]*?PATTERN2/msg ) { print $1 if ++$x==2 } ' ip.txt
2
46

avec Perl

awk -v x=0 -v y=2 ' /PATTERN1/ { x++;next } /PATTERN2/ { if(x==y) exit } x==y ' ip.txt
2
46

Résultats:

$ cat ip.txt
aaa
PATTERN1
bbb
ccc
ddd
PATTERN2
eee
PATTERN1
2
46
PATTERN2
xyz

$

$ awk -v x=0 -v y=1 ' /PATTERN1/&&y { x=1;next } /PATTERN2/&&y { x=0;y=0; next } x ' ip.txt
bbb
ccc
ddd

$ perl -0777 -ne ' while( /PATTERN1.*?\n(.+?)^[^\n]*?PATTERN2/msg ) { print $1 if $x++ <1 } ' ip.txt
bbb
ccc
ddd

$

Pour y parvenir générique

awk ici y est l'entrée

perl -0777 -ne ' while( /PATTERN1.*?\n(.+?)^[^\n]*?PATTERN2/msg ) { print $1 if $x++ <1 } '

perl check ++ $ x contre l'occurrence .. ici c'est 2

awk -v x=0 -v y=1 ' /PATTERN1/&&y { x=1;next } /PATTERN2/&&y { x=0;y=0; next } x ' file


0 commentaires

1
votes

Ajouter plus de solutions (manières possibles ici, pour le plaisir :) et ne pas prétendre du tout que celles-ci sont meilleures que les habituelles) Toutes testées et écrites en GNU awk . Également testé avec des exemples donnés uniquement.

1ère solution:

bbb
ccc
ddd

2ème solution :

awk -v RS="" -v OFS="\n" -v ORS="" 'sub(/PATTERN2.*/,"") && sub(/.*PATTERN1/,"PATTERN1"){$1=$1;sub(/^PATTERN1\n/,"")} 1' Input_file

3e solution:

awk -v RS="" -v ORS="" 'match($0,/PATTERN1[^(PATTERN2)]*/){val=substr($0,RSTART,RLENGTH);gsub(/^PATTERN1\n|^$\n/,"",val);print val}' Input_file


0 commentaires

1
votes

En utilisant GNU sed:

sed -nE '/PATTERN1/{:s n;/PATTERN2/q;p;bs}'

-n élagera toutes les lignes sauf les lignes entre PATTERN1 et PATTERN2, y compris les deux, car il y aura p commande d'impression. chaque intervalle sed vérifier si c'est vrai n'exécutera qu'un seul le suivant, donc {} le groupement est obligatoire. Supprimez PATTERN1 par la commande n (signifie suivant), si vous atteignez le premier PATTERN2, quittez carrément, sinon imprimez la ligne puis continuez la ligne suivante dans cette limite.


0 commentaires