Comment extraire plusieurs lignes d'un fichier par une expression régulière?
Je voudrais souvent obtenir plusieurs lignes / modifier plusieurs lignes par une expression régulière. Un exemple de cas:
J'essaie de lire une partie d'un fichier XML / SGML (ils ne sont pas nécessairement bien formés ou dans une syntaxe prévisible, donc une expression régulière serait plus sûre qu'un analyseur approprié. De plus, j'aimerais pouvoir le faire aussi complètement fichiers non structurés où seuls quelques mots clés sont connus.) dans un script shell (fonctionnant sous Solaris et Linux).
Exemple XML:
<tag1>
<tag2>bar</tag2>
</tag1>
<tag1>
<tag2>foo</tag2>
</tag1>
De cela, je voudrais lire <tag1>
si elle contient foo
quelque part en elle.
Un regex comme (<tag1>.*?foo.*?</tag1>)
devrait donner la bonne partie mais des outils comme grep
et sed
ne fonctionnent que pour moi avec des lignes simples. Comment puis-je avoir
<tag1>
<tag2>foo</tag2>
</tag1>
dans cet exemple?
Réponses:
Si GNU grep est installé, vous pouvez faire une recherche multiligne en passant le
-P
drapeau (perl-regex) et en activantPCRE_DOTALL
avec(?s)
Si ce qui précède ne fonctionne pas sur votre plate-forme, essayez de passer le
-z
drapeau en plus, cela force grep à traiter NUL comme séparateur de ligne, ce qui fait que le fichier entier ressemble à une seule ligne.la source
(?s)
astuce(GNU grep) 2.14
sur Debian. J'ai copié l'exemple OPs telgrep
quel (en ajoutant uniquement la nouvelle ligne finale) et je l'ai exécuté sans obtenir de résultats.grep -ozP
plutôt quegrep -oP
sur vos plateformes?Si vous faites ce qui précède, compte tenu des données que vous affichez, avant cette dernière ligne de nettoyage, vous devriez travailler avec un
sed
espace de modèle qui ressemble à:Vous pouvez imprimer votre espace de motif à tout moment avec
l
ook. Vous pouvez ensuite vous adresser aux\n
personnages.Vous montrera que chaque ligne la
sed
traite au stade où ellel
est appelée.Je viens donc de le tester et il en fallait un de plus
\backslash
après,comma
la première ligne, mais sinon il fonctionne tel quel . Ici, je le mets dans un_sed_function
afin que je puisse facilement l'appeler à des fins de démonstration tout au long de cette réponse: (fonctionne avec les commentaires inclus, mais sont supprimés ici par souci de concision)Maintenant, nous allons changer le
p
pour unl
afin que nous puissions voir avec quoi nous travaillons pendant que nous développons notre script et supprimons la démo non-ops?
pour que la dernière ligne de notresed 3<<\SCRIPT
ressemble à ceci :Ensuite, je vais l'exécuter à nouveau:
D'accord! J'avais donc raison - c'est un bon sentiment. Maintenant, mélangeons notre
l
regard pour voir les lignes qu'il tire mais supprime. Nous allons supprimer notre courantl
et en ajouter un pour!{block}
qu'il ressemble à ceci:Voilà à quoi cela ressemble juste avant de l'effacer.
Une dernière chose que je veux vous montrer est l'
H
ancien espace que nous construisons. Il y a quelques concepts clés que j'espère pouvoir démontrer. Je retire donc le dernierl
ook et modifie la première ligne pour ajouter un aperçu dans l'H
ancien espace à la fin:H
le vieil espace survit aux cycles des lignes - d'où son nom. Donc, ce que les gens font souvent trébucher - ok, ce que je fais souvent trébucher - c'est qu'il faut le supprimer après l'avoir utilisé. Dans ce cas, je nex
change qu'une seule fois, donc l'espace de maintien devient l'espace de motif et vice-versa et ce changement survit également aux cycles de ligne.L'effet est que je dois supprimer mon espace d'attente qui était mon espace de motif. Pour ce faire, je vide d'abord l'espace de motif actuel avec:
Qui sélectionne simplement chaque personnage et le supprime. Je ne peux pas l'utiliser
d
car cela mettrait fin à mon cycle de ligne en cours et la prochaine commande ne se terminerait pas, ce qui mettrait à peu près à la poubelle mon script.Cela fonctionne de manière similaire à
H
mais il écrase l' espace de rétention , donc je viens de copier mon espace de motif vierge au-dessus de mon espace de rétention, le supprimant efficacement. Maintenant je peux juste:en dehors.
Et c'est comme ça que j'écris des
sed
scripts.la source
La réponse de @ jamespfinn fonctionnera parfaitement bien si votre fichier est aussi simple que votre exemple. Si vous avez une situation plus complexe qui
<tag1>
peut s'étendre sur plus de 2 lignes, vous aurez besoin d'une astuce légèrement plus complexe. Par exemple:Le script perl traitera chaque ligne de votre fichier d'entrée et
if(/<tag1>/){$a=1;}
: la variable$a
est définie sur1
si une balise d'ouverture (<tag1>
) est trouvée.if($a==1){push @l,$_}
: pour chaque ligne, si$a
c'est le cas1
, ajoutez cette ligne au tableau@l
.if(/<\/tag1>/)
: si la ligne actuelle correspond à la balise de fermeture:if(grep {/foo/} @l){print "@l"}
: si l'une des lignes enregistrées dans le tableau@l
(ce sont les lignes entre<tag1>
et</tag1>
) correspond à la chaînefoo
, imprimez le contenu de@l
.$a=0; @l=()
: vide la liste (@l=()
) et$a
remet à 0.la source
<tag1>
avecfoo
et cela fonctionne très bien. Quand cela échoue-t-il pour vous?Voici une
sed
alternative:Explication
-n
signifie ne pas imprimer de lignes sauf indication contraire./<tag1/
correspond d'abord à la balise d'ouverture:x
est une étiquette pour permettre de sauter à ce point plus tardN
ajoute la ligne suivante à l'espace de motif (tampon actif)./<\/tag1/!b x
signifie que si l'espace de motif actuel ne contient aucune balise de fermeture, branchez-vous sur l'x
étiquette créée précédemment. Nous continuons donc à ajouter des lignes à l'espace de motif jusqu'à ce que nous trouvions notre balise de fermeture./foo/p
signifie que si l'espace de motif actuel correspondfoo
, il doit être imprimé.la source
Vous pouvez le faire avec GNU awk je pense, en traitant la balise de fin comme un séparateur d'enregistrement, par exemple pour une balise de fin connue
</tag1>
:ou plus généralement (avec une expression régulière pour la balise de fin)
Le tester sur @ terdon
foo.xml
:la source
Si votre fichier est structuré exactement comme vous l'avez montré ci-dessus, vous pouvez utiliser les indicateurs -A (lignes après) et -B (lignes avant) pour grep ... par exemple:
Si votre version de le
grep
prend en charge, vous pouvez également utiliser l'-C
option plus simple (pour le contexte) qui imprime les N lignes environnantes:la source
tail -3 input_file.xml
. Oui, cela fonctionne pour cet exemple spécifique, mais ce n'est pas une réponse utile à la question.