Forum Programmation.ruby Expressions régulières

Posté par  .
Étiquettes : aucune
0
11
juin
2008
Bonjour,

j'ai un soucis avec des expressions régulières Ruby. Je n'ai jamais réussi à avoir une chaîne vide en cas de non correspondance... uniquement si la chaîne vient d'un fichier (?!)

Par exemple je souhaite lire un fichier de configuration structuré comme suis:

# commentaire
PARAMETRE = VALEUR
AUTRE_PARAMETRE = AUTRE_VALEUR
ENCORE_UN = 123 # commentaire
UNE_RUSE AHAH

La dernière ligne contient volontairement une erreur. Il manque le signe '='.

Si je veux lire le nom du paramètre (en supprimant les blancs avant et après):

chaine = "AUTRE_PARAMETRE = AUTRE_VALEUR"
print chaine.sub!(/^\s*(.*)\s*=.*$/, '\1'), "\n"
==> AUTRE_PARAMETRE
chaine = "UNE_RUSE AHAH"
print chaine.sub!(/^\s*(.*)\s*=.*$/, '\1'), "\n"
==> nil

La première ligne affichée est bonne. La seconde affiche 'nil' ce qui est normal puisqu'il manque le signe '=', donc aucune correspondance n'est trouvée.


Maintenant je veux le lire "en vrai":

IO.readlines("fichier.conf").each do |ligne|

if ligne =~ /^\s*[;#]+|^\s*$/
next # sauter les lignes vides (vides, ou espaces, ou tabulations, ou commentaires)
end

print ligne.sub(/^\s*(.*)\s*=.*$/, '\1'), "\n"

end


==> PARAMETRE
==> AUTRE_PARAMETRE
==> ENCORE_UN
==> UNE_RUSE AHAH

La dernière ligne devrait être 'nil'. Je ne comprends pas pourquoi elle n'est pas 'nil' alors que c'est la même expression que dans le premier test. Je ne sais pas comment faire pour que ça retourne 'nil' ou une chaîne vide si il n'y a pas le signe '='.

Quelqu'un a une idée ?
  • # manque bang ?!

    Posté par  . Évalué à 1.

    • [^] # Re: manque bang ?!

      Posté par  . Évalué à 2.

      Je suis un boulet :-) J'ai mélangé avec et sans point d'exclamation. A force de buter sur le problème, de copier/coller etc.

      Il me reste comme problème: je ne veux pas utiliser '!' justement, car j'ai encore besoin de la chaine de caractères complète par la suite.
      Je souhaite faire:
      parametre = ligne.sub(/^\s*(.*)\s*=.*$/, '\1')
      valeur = ligne.sub(/=\s*(.*)\s*$/, '\1')
      ... et d'autre choses.

      Je remarque également: si il y a plus d'un signe '=' sur la ligne, ce qui est retourné est incorrect. Ce n'est pas génant pour ce que je souhaite lire, mais intellectuellement ça me tracasse.
      Si la ligne est
      PARAM = 1+3=4
      Alors ça me retourne
      parametre = PARAM = 1+3
      alors que j'aimerais
      parametre = PARAM
      • [^] # Re: manque bang ?!

        Posté par  (site web personnel) . Évalué à 2.

        Pour le second problème, utilise la capture non-greedy en ajoutant un "?" après ton "*" :
        ^\s*(.*)\s*=.*$||
        V
        ^\s*(.*?)\s*=.*$
      • [^] # Re: manque bang ?!

        Posté par  . Évalué à 4.

        Deux principes :
        1. Comme tu veux les deux valeurs autour du =, il est plus logique de ne faire qu’une fois le boulot (une seule fois la même expression régulière).
        2. Quand on veut s’arrêter au premier caractère X, l’expression régulière qui précède X se base sur [^X]*, pas sur .*.

        Résultat :

        File.open('test.txt').each_line do |line|
          line.sub!(/[;#].*/, '') # enlever les commentaires de fin de ligne
          line.strip!             # enlever les espaces en trop
          next if line.empty?
          if line =~ /\s*([^=\s]*)\s*=\s*([^\s]*)/
            name, value = $1, $2
            print ".#{name}. == .#{value}.\n"
          end
        end


        La variable value n’est jamais nil car $2 peut prendre la chaîne vide.
        • [^] # Re: manque bang ?!

          Posté par  . Évalué à 2.

          Avec l'indication d'Antoine Millet j'ai pu contourner le problème des multiples signes '=' et même des espaces à l'intérieur des chaînes. Il ne me reste plus qu'à comprendre comment fonctione ce *? car les quelques pages web que j'ai consulté ne sont pas explicites. Je me penche là dessus demain :-)

          L'indication "1" de Sylvain Sauvage m'a fait modifier mon code pour utiliser $1 et $2. Excellente idée. Pour l'indication "2" soit j'ai mal testé, soit ça ne fonctionne pas dans les cas tordus.


          IO.readlines("test.conf").each do |ligne|

          if ligne =~ /^\s*[;#]+|^\s*$/
          next # sauter les lignes vides (vides, ou espaces, ou tabulations, ou commentaires uniquement)
          end

          if ligne =~ /^\s*(.*?)\s*=\s*(.*?)\s*[\n]$/
          nom, valeur = $1, $2
          print "[", nom, "] = [", valeur, "]\n"
          end

          end

          note: comment vous faites pour indenter entre les balises 'code' ?

          Ce code fonctionne maintenant pour toutes les situations tordues que j'ai imaginé: avec des espaces à la fin, avec plusieurs signes '=', avec le nom manquant, etc. Le résultat est soit 'nil' soit une valeur correcte.

          Je vais maintenant supprimer les commentaires de fin de ligne. Une simple détection de ; et de # suffit en principe. Je vais juste ajouter la possibilité d'avoir un caractère d'échappement et la gestion des guillemets. Inutile pour smb.conf mais pas pour le futur.
          Par exemple si je veux un parametre pouvant contenir n'importe quelle chaîne de caractères:
          MESSAGE = "## ATTENTION: erreur détectée ; valeur trop élevée ###"
          Cet exemple contient ; et #
          • [^] # Re: manque bang ?!

            Posté par  . Évalué à 2.

            Pour l’indentation du code : je mets des espaces insécables :oP

            Pour le 2., mon code (qui élimine les commentaires et les espaces dès le départ) fonctionne sur les exemples que tu as donnés. Tu as des exemples qui ne passent pas ?

            Pour *? et +?, ils se différencient de * et + par le fait que * et + essaient de correspondre à la plus longue chaîne possible alors que *? et +? s’arrêtent dès qu’ils peuvent. (* et + sont dits greedy, *? et +? lazy.) Exemple classique : pour attraper une balise HTML (et juste la balise), on utilise /<.+?>/ car /<.+>/ va tout gober entre la première et la dernière balises du texte.

            Utiliser la négation ([^X]*) est moins gourmande car le moteur n’a pas besoin de faire du backtrack (ce qu’il doit faire avec .*?). Je trouve ça aussi plus clair.

            Pour les guillemets, il va peut-être falloir utiliser autre chose (une grammaire p.ex., au moins des états), ne serait-ce que parce qu’il faudra aussi gérer les sauts de ligne entre les guillemets.
            • [^] # Re: manque bang ?!

              Posté par  . Évalué à 2.

              mon code (qui élimine les commentaires et les espaces dès le départ) fonctionne sur les exemples que tu as donnés. Tu as des exemples qui ne passent pas ?
              Au pif:
              MESSAGE_ERREUR = "Bim-badaboum ; erreur entre deux balises dièse (#)"

              Pour les guillemets je ne gèrerais pas les sauts de lignes puisque je ne les gère pas non plus pour le reste. Il va falloir que je tienne "seulement" compte des paires de guillemets, des caractères d'échapement, et des guillemets précédés d'un caractère d'échapement. Je cherche les problèmes :-)

              CHAINE_BIDON = "ma chaîne \" avec un piège au milieu"
  • # Solution compliquée

    Posté par  . Évalué à 1.

    Pourquoi créer ton propre format de fichier, alors qu'en une ligne de code, tu peux parser du xml, du json, et du yaml ?

    En plus, t'auras une gestion des erreurs toute faîte.

    Envoyé depuis mon lapin.

    • [^] # Superbe solution :-)

      Posté par  . Évalué à 1.

      Comme c'est pour lire smb.conf il ne te reste plus qu'à aller dire aux développeurs de Samba qu'il sont vraiment crétins de ne pas utiliser les merveilleux formats que tu proposes.

Suivre le flux des commentaires

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