Forum Programmation.shell regex de recherche de lignes coupées

Posté par  .
Étiquettes : aucune
1
26
mai
2011

J'ai un fichier en entrée contenant des lignes du type :
<balise>champs#séparés#par#des#dièses</balise>
Le problème est que ces lignes sont parfois coupées :
<balise>champs#sép
arés#par#des#dièses</balise>

La coupure peut se trouver à n'importe quel endroit dans la ligne.
J'aimerais recoller les deux morceaux pour pouvoir donner une ligne complète à mon awk qui suit, mais je n'arrive pas à trouver une regex pour sed pour matcher ces lignes coupées et les recoller.

Quelqu'un a une idée ?

sed n'est pas une obligation, j'ai aussi essayé avec awk à coups de getline, mais l'autre problème qui se pose, c'est que j'ai des lignes délimitées par des <balise></balise> dont le contenu est différent, et que je ne dois pas traiter. Si je les envoie toutes à awk, il faut alors que je matche un motif dans le contenu de la ligne (genre 'champs' dans l'exemple) pour ne traiter que les lignes qui m'intéressent, sachant que ce motif peut-être coupé en deux... Ce qui m'amène donc à dire qu'il faut d'abord que je recolle mes lignes coupées avant de décider si je dois les traiter avec awk, d'où l'idée du sed...

  • # Un truc du genre

    Posté par  . Évalué à 3.

    1 - supprimer tous les retours à la ligne
    2 - ajouter un retour à la ligne aux bons endroits

    Exemple non testé:
    echo $(cat mon_fichier.txt) | sed 's/> </n/g'

    • [^] # Re: Un truc du genre

      Posté par  . Évalué à 3.

      Mon exemple est totalement faux car echo ajoute un espace à la place des sauts de ligne.
      Avec tr comme indiqué plus bas:

      tr fichier.txt -d 'n' | sed 's/></n/g'

      Je pense qu'on peut le faire en un seul appel à sed ou awk, je n'ai pas la compétence pour trouver.
      En gros: si la fin de ligne n'est pas >$ alors supprimer $.

  • # une piste

    Posté par  . Évalué à 3.

    $ cat exemple 
    <balise>champs#séparés#par#des#dièses</balise>
    <balise>champs#sép
    arés#par#des#dièses</balise>
    
    $ sed -s '/<balise>.*<\/balise>/ {n}
    N
    s/\n//
    ' exemple 
    <balise>champs#séparés#par#des#dièses</balise>
    <balise>champs#séparés#par#des#dièses</balise>
    

    ça passe avec l'exemple proposé, mais c'est loin d'être parfait. Si une ligne est découpée sur plus de deux lignes, ou si le retour à la ligne découpe une balise, ça ne suffit pas.

    Si tu as plusieurs jeux de balise possible, c'est aussi un peu court en l'état. A ce moment là, et je suis le premier étonné, ce qui suit à l'air de marcher =)

    sed -s '/<\(.*\)>.*<\/\1>/ {n}
    N
    s/\n//
    ' exemple 
    
  • # Un marque page que je garde toujours sous le coude…

    Posté par  . Évalué à 3.

    Il y a une bonne explication de solution avec « sed » ici : http://www.ilfilosofo.com/blog/2008/04/26/sed-multi-line-search-and-replace/

  • # tr

    Posté par  . Évalué à 1.

    La commande "tr" permet de remplacer / supprimer des caractères. La ligne suivante supprime tout les saut de lignes de fichier.txt

    bash$ tr fichier.txt -d '\n'
    
  • # pb résolu, merci à vos idées qui m'ont été utiles

    Posté par  . Évalué à 0.

    J'ai fini par mettre un peu de perl et de tr.
    ça se passe en deux phases :
    - suppression de tous les retours chariot, ce qui a pour effet de mettre tout le fichier sur une seule ligne, mais aussi et surtout de recoller les lignes coupées qui m'intéressent. Cette étape est faite avec un simple tr -d '\n'
    - ajout de retours chariot avant <balise> et après </balise> pour isoler les lignes que je veux récupérer. Cette fois, c'est du perl. J'ai essayé avec sed, mais sous aix, impossible de lui faire insérer un retour chariot. Le \n n'est pas interprété, le \x0A non plus. Si quelqu'un a une explication, ça m'intéresse.
    Le code perl est simple (je découvre complètement perl, c'est sûrement très moche) :
    perl -e 'while(<>){s/(<balise>)([^<]+)(</balise>)/\n\2\n/g;print;}'
    Oui, je sais c'est idiot, c'est exactement ce pour quoi sed est fait, mais comme dit plus haut, je n'ai pas réussi à lui faire écrire des retours chariot sous aix.
    La commande de substitution se décompose comme ceci :
    - <balise> : bah, on recherche la chaine de caractères telle quelle
    - [^<]+ : un ou plusieurs caractères, n'importe lequel sauf "<". Ici, j'aurais pu mettre .*, mais dans ce cas, perl cherche à faire la plus grande chaine qui matche. Je me retrouverais donc avec une chaine commençant par la première occurrence de "<balise>" du fichier et finissant par la dernière occurrence de "</balise>", ce qui ne fonctionnerait que si le fichier en entrée ne contient qu'une et une seule fois la balise en question (ce qui n'est bien sûr pas le cas).
    - </balise> : comme le premier bloc, on recherche la chaine de caractères telle quelle.

    On substitue ce qui matche par "\n\2\n", c'est à dire deux retours chariot encadrant le deuxième bloc dans la chaine qui matche le pattern. Comme ça, d'un coup, je supprime les balise pour ne récupérer que le contenu, isolé sur une ligne.

    La suite du traitement est un grep sur une chaine que je retrouve uniquement dans les lignes qui m'intéressent, suivi d'un awk qui saucissonne le contenu et me le crache sous forme de sql que je peux jouer directement.
    Tout ça manque certainement d'élégance (par exemple, dans la substitution, je peux sûrement me passer des parenthèses autour de "<balise>" et "</balise>", vu que je ne les réutilise pas dans la chaine de substitution...), j'aurais bien voulu du coup intégrer le tr et le grep au perl, mais ça dépasse mes compétences dans ce langage...
    Encore une fois, si vous avez des idées sur le sujet...

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.