Comment sélectionner des lignes entre deux motifs de marqueurs qui peuvent apparaître plusieurs fois avec awk / sed

119

En utilisant awkou sedcomment puis-je sélectionner des lignes qui se produisent entre deux motifs de marqueurs différents? Il peut y avoir plusieurs sections marquées de ces modèles.

Par exemple: supposons que le fichier contienne:

abc
def1
ghi1
jkl1
mno
abc
def2
ghi2
jkl2
mno
pqr
stu

Et le modèle de départ est abcet le modèle de fin est mno donc, j'ai besoin de la sortie comme:

def1
ghi1
jkl1
def2
ghi2
jkl2

J'utilise sed pour faire correspondre le modèle une fois:

sed -e '1,/abc/d' -e '/mno/,$d' <FILE>

Existe-t-il un moyen sedou awk de le faire à plusieurs reprises jusqu'à la fin du fichier?

dvai
la source

Réponses:

188

À utiliser awkavec un drapeau pour déclencher l'impression si nécessaire:

$ awk '/abc/{flag=1;next}/mno/{flag=0}flag' file
def1
ghi1
jkl1
def2
ghi2
jkl2

Comment cela marche-t-il?

  • /abc/correspond aux lignes contenant ce texte, ainsi que le /mno/fait.
  • /abc/{flag=1;next}définit le flagmoment où le texte abcest trouvé. Ensuite, il saute la ligne.
  • /mno/{flag=0}annule le flaglorsque le texte mnoest trouvé.
  • Le final flagest un motif avec l'action par défaut, qui est de print $0: si flagest égal à 1, la ligne est imprimée.

Pour une description plus détaillée et des exemples, ainsi que des cas où les motifs sont affichés ou non, voir Comment sélectionner des lignes entre deux motifs? .

fedorqui 'Alors arrêtez de nuire'
la source
30
Si vous souhaitez tout imprimer entre et y compris le motif, vous pouvez utiliser awk '/abc/{a=1}/mno/{print;a=0}a' file.
scai
6
Oui, @scai! ou même awk '/abc/{a=1} a; /mno/{a=0}' file- avec cela, mettre la acondition avant le /mno/nous lui faisons évaluer la ligne comme vraie (et l'imprimer) avant de la définir a=0. De cette façon, nous pouvons éviter d'écrire print.
fedorqui 'SO arrêtez de nuire'
12
@scai @fedorqui Pour inclure la sortie de motif, vous pouvez le faireawk '/abc/,/mno/' file
Jotne
1
@hkasera awk '/abc/{flag=1}/mno/{flag=0}flag' filedevrait faire.
fedorqui 'SO arrêtez de nuire'
2
@EirNym c'est un scénario bizarre qui peut être géré de différentes manières: quelles lignes aimeriez-vous imprimer? Le awk 'flag; /PAT1/{flag=1; next} /PAT1/{flag=0}' fileferait probablement .
fedorqui 'SO arrêtez de nuire'
45

Utilisation sed:

sed -n -e '/^abc$/,/^mno$/{ /^abc$/d; /^mno$/d; p; }'

L' -noption signifie ne pas imprimer par défaut.

Le modèle recherche les lignes contenant juste abcà juste mno, puis exécute les actions dans le { ... }. La première action supprime la abcligne; le second la mnoligne; et les pimprime les lignes restantes. Vous pouvez assouplir les expressions régulières au besoin. Les lignes en dehors de la plage de abc.. ne mnosont tout simplement pas imprimées.

Jonathan Leffler
la source
Merci pour la réponse et pour l'explication! :)
dvai
@JonathanLeffler puis-je savoir quel est le but de l'utilisation-e
Kasun Siyambalapitiya
1
@KasunSiyambalapitiya: Cela signifie principalement que j'aime l'utiliser. Formellement, il spécifie que l'argument suivant fait (partie) du script qui seddoit s'exécuter. Si vous voulez ou devez utiliser plusieurs arguments pour inclure le script entier, vous devez utiliser -eavant chacun de ces arguments; sinon, c'est facultatif (mais explicite).
Jonathan Leffler
@JonathanLeffler Thanks
Kasun Siyambalapitiya
Agréable! (Je préfère sed à awk.) Lorsque vous utilisez des expressions régulières complexes, ce serait bien de ne pas avoir à les répéter. N'est-il pas possible de supprimer la première / dernière ligne de la plage "sélectionnée"? Ou appliquer d'abord le dà toutes les lignes jusqu'au premier match, puis un autre dà toutes les lignes commençant par le deuxième match?
hans_meine
18

Cela pourrait fonctionner pour vous (GNU sed):

sed '/^abc$/,/^mno$/{//!b};d' file

Supprimez toutes les lignes sauf celles entre les lignes commençant abcetmno

potong
la source
!d;//dgolfs 2 caractères mieux :-) stackoverflow.com/a/31380266/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
C'est génial. L' {//!b}empêche abcet mnod'être inclus dans la sortie, mais je ne peux pas comprendre comment. Pourriez-vous expliquer?
Brendan
1
@Brendan l'instruction //!blit si la ligne courante n'est ni l'une des lignes qui correspondent à la plage, coupez et donc imprimez ces lignes sinon toutes les autres lignes sont supprimées.
potong
13
sed '/^abc$/,/^mno$/!d;//d' file

golfs deux personnages mieux que ceux de ppotong {//!b};d

Les barres obliques vides //signifient: "réutiliser la dernière expression régulière utilisée". et la commande fait la même chose que la plus compréhensible:

sed '/^abc$/,/^mno$/!d;/^abc$/d;/^mno$/d' file

Cela semble être POSIX :

Si un RE est vide (c'est-à-dire qu'aucun modèle n'est spécifié), sed doit se comporter comme si le dernier RE utilisé dans la dernière commande appliquée (soit en tant qu'adresse, soit en tant que partie d'une commande de substitution) était spécifié.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
1
Je pense que la deuxième solution se terminera par rien car la deuxième commande est aussi une plage. Cependant bravo pour le premier.
potong
@potong vrai! Je dois étudier davantage pourquoi le premier fonctionne. Merci!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
7

À partir des liens de la réponse précédente, celui qui l'a fait pour moi, fonctionnant kshsous Solaris, était le suivant:

sed '1,/firstmatch/d;/secondmatch/,$d'
  • 1,/firstmatch/d: de la ligne 1 jusqu'à la première recherche firstmatch, supprimez.
  • /secondmatch/,$d: de la première occurrence de secondmatchjusqu'à la fin du fichier, supprimer.
  • Le point-virgule sépare les deux commandes, qui sont exécutées dans l'ordre.
FanDeLaU
la source
Juste curieux, pourquoi le limiteur de plage ( 1,) vient-il avant /firstmatch/? Je suppose que cela pourrait également être formulé '/firstmatch/1,d;/secondmatch,$d'?
Luke Davis
2
Avec "1, / firstmatch / d" vous dites "de la ligne 1 jusqu'à la première fois que vous trouvez" firstmatch ", delete". Alors que, avec "/ secondmatch /, $ d" vous dites "de la première occurrence de" secondmatch "jusqu'à la fin du fichier, supprimez". le point-virgule sépare les deux commandes, qui sont exécutées dans l'ordre.
FanDeLaU
2
perl -lne 'print if((/abc/../mno/) && !(/abc/||/mno/))' your_file
Vijay
la source
Bon à savoir l'équivalent perl car c'est une très bonne alternative à awk et sed.
akhan
2

quelque chose comme ça fonctionne pour moi:

file.awk:

BEGIN {
    record=0
}

/^abc$/ {
    record=1
}

/^mno$/ {
    record=0;
    print "s="s;
    s=""
}

!/^abc|mno$/ {
    if (record==1) {
        s = s"\n"$0
    }   
}

en utilisant: awk -f file.awk data...

edit: La solution O_o fedorqui est bien meilleure / plus jolie que la mienne.

Pataluc
la source
3
Dans GNU awk if (record=1)devrait être if (record==1), c'est-à-dire double = - voir les opérateurs de comparaison gawk
George Hawkins
2

Réponse de Don_crissti dans Afficher uniquement le texte entre 2 motifs correspondants ?

firstmatch="abc"
secondmatch="cdf"
sed "/$firstmatch/,/$secondmatch/!d;//d" infile

qui est beaucoup plus efficace que l'application d'AWK, voir ici .

Léo Léopold Hertz 준영
la source
Je ne pense pas que lier les comparaisons de temps ait beaucoup de sens ici, car les exigences des questions sont très différentes, d'où les solutions.
fedorqui 'SO arrêtez de nuire' le
2
Je ne suis pas d'accord car nous devrions avoir des critères pour comparer les réponses. Seuls quelques-uns ont des applications SED.
Léo Léopold Hertz 준영
0

J'ai essayé d' awkimprimer des lignes entre deux motifs alors que pattern2 correspond également à pattern1 . Et la ligne pattern1 doit également être imprimée.

par exemple source

package AAA
aaa
bbb
ccc
package BBB
ddd
eee
package CCC
fff
ggg
hhh
iii
package DDD
jjj

devrait avoir une sortie de

package BBB
ddd
eee

Où pattern1 est package BBB, pattern2 est package \w*. Notez que ce CCCn'est pas une valeur connue et ne peut donc pas être mis en correspondance.

Dans ce cas, ni @scai awk '/abc/{a=1}/mno/{print;a=0}a' fileni @fedorqui ne awk '/abc/{a=1} a; /mno/{a=0}' filefonctionnent pour moi.

Enfin, j'ai réussi à le résoudre par awk '/package BBB/{flag=1;print;next}/package \w*/{flag=0}flag' file, haha

Un peu plus d'effort permet awk '/package BBB/{flag=1;print;next}flag;/package \w*/{flag=0}' filed'imprimer également la ligne pattern2, c'est-à-dire

package BBB
ddd
eee
package CCC
Weekend
la source