Extraire une expression rationnelle associée à «sed» sans imprimer les caractères environnants

24

À tous les médecins «sed» du monde:

Comment pouvez-vous obtenir «sed» pour extraire une expression régulière à laquelle il correspond dans une ligne?

En d'autres termes, je veux juste la chaîne correspondant à l'expression régulière avec tous les caractères non correspondants de la ligne contenant supprimés.

J'ai essayé d'utiliser la fonction de référence arrière comme ci-dessous

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*\( \).*/\1/p 

cela fonctionne pour certaines expressions comme

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

qui extrait soigneusement tous les noms de macro commençant par 'CONFIG_ ....' (trouvé dans certains fichiers '* .h') et les imprime tous ligne par ligne

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

MAIS ce qui précède se décompose pour quelque chose comme

  sed -n 's/.*\([0-9][0-9]*\).*/\1/p 

cela renvoie toujours des chiffres uniques comme

                 7
                 9
                 .
                 .  
                 6

plutôt que d'extraire un champ numérique contigu tel que.

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

PS: Je serais reconnaissant aux commentaires sur la façon dont cela est réalisé dans «sed». Je sais comment faire avec « grep » et « awk » Je voudrais savoir si mon - bien que limitée - la compréhension des sed a des trous dans et s'il y a moyen de le faire dans sed que
j'ai tout simplement négligé.

darbehdar
la source

Réponses:

22

Lorsqu'une expression rationnelle contient des groupes, il peut y avoir plusieurs façons de faire correspondre une chaîne à celle-ci: les expressions rationnelles avec des groupes sont ambiguës. Par exemple, considérez l'expression rationnelle ^.*\([0-9][0-9]*\)$et la chaîne a12. Il y a deux possibilités:

  • Match acontre .*et 2contre [0-9]*; 1correspond à [0-9].
  • Match a1contre .*et la chaîne vide contre [0-9]*; 2correspond à [0-9].

Sed, comme tous les autres outils d'expression régulière, applique la règle de correspondance la plus longue la plus ancienne: il essaie d'abord de faire correspondre la première partie de longueur variable à une chaîne aussi longue que possible. S'il trouve un moyen de faire correspondre le reste de la chaîne avec le reste de l'expression rationnelle, très bien. Sinon, sed essaie la prochaine correspondance la plus longue pour la première partie de longueur variable et réessaye.

Ici, la correspondance avec la chaîne la plus longue est d'abord a1contre .*, donc le groupe ne correspond que 2. Si vous voulez que le groupe démarre plus tôt, certains moteurs d'expression régulière vous permettent de rendre le .*moins gourmand, mais sed n'a pas une telle fonctionnalité. Vous devez donc supprimer l'ambiguïté avec un ancrage supplémentaire. Spécifiez que l'interligne .*ne peut pas se terminer par un chiffre, de sorte que le premier chiffre du groupe soit la première correspondance possible.

  • Si le groupe de chiffres ne peut pas être au début de la ligne:

    sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
    
  • Si le groupe de chiffres peut être au début de la ligne et que votre sed prend en charge l' \?opérateur pour les pièces optionnelles:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • Si le groupe de chiffres peut être au début de la ligne, respectez les constructions d'expression régulière standard:

    sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -e t -e 's/^\([0-9][0-9]*\).*/\1/p'
    

Soit dit en passant, c'est la même règle de correspondance la plus ancienne qui fait [0-9]*correspondre les chiffres après le premier, plutôt que les suivants .*.

Notez que s'il y a plusieurs séquences de chiffres sur une ligne, votre programme extraira toujours la dernière séquence de chiffres, toujours en raison de la règle de correspondance la plus longue la plus ancienne appliquée à l'initiale .*. Si vous souhaitez extraire la première séquence de chiffres, vous devez spécifier que ce qui précède est une séquence de non-chiffres.

sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'

Plus généralement, pour extraire la première correspondance d'une expression rationnelle, vous devez calculer la négation de cette expression rationnelle. Bien que cela soit toujours théoriquement possible, la taille de la négation croît de façon exponentielle avec la taille de l'expression rationnelle que vous niez, donc cela est souvent peu pratique.

Considérez votre autre exemple:

sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'

Cet exemple présente en fait le même problème, mais vous ne le voyez pas sur les entrées typiques. Si vous l'alimentez hello CONFIG_FOO_CONFIG_BAR, la commande ci-dessus s'imprime CONFIG_BAR, non CONFIG_FOO_CONFIG_BAR.

Il existe un moyen d'imprimer le premier match avec sed, mais c'est un peu délicat:

sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p

(En supposant que sed prend \nen charge pour signifier une nouvelle ligne dans le stexte de remplacement.) Cela fonctionne parce que sed recherche la correspondance la plus ancienne de l'expression rationnelle, et nous n'essayons pas de faire correspondre ce qui précède le CONFIG_…bit. Puisqu'il n'y a pas de nouvelle ligne à l'intérieur de la ligne, nous pouvons l'utiliser comme marqueur temporaire. La Tcommande dit d'abandonner si la scommande précédente ne correspond pas.

Lorsque vous ne savez pas comment faire quelque chose dans sed, tournez-vous vers awk. La commande suivante imprime la correspondance la plus longue et la plus ancienne d'une expression rationnelle:

awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'

Et si vous souhaitez rester simple, utilisez Perl.

perl -l -ne '/[0-9]+/ && print $&'       # first match
perl -l -ne '/^.*([0-9]+)/ && print $1'  # last match
Gilles 'SO- arrête d'être méchant'
la source
22

Bien que ce ne soit pas le cas sed, l'une des choses souvent négligées est celle grep -oqui, à mon avis, est le meilleur outil pour cette tâche.

Par exemple, si vous souhaitez obtenir tous les CONFIG_paramètres d'une configuration du noyau, vous utiliserez:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

Si vous souhaitez obtenir des séquences de nombres contigus:

$ grep -Eo '[0-9]+' foo
Patrick
la source
7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... fera cela sans problème, bien que vous ayez besoin de nouvelles lignes littérales à la place des ns dans le champ de substitution de droite. Et, en passant, la .*CONFIGchose ne fonctionnerait que s'il n'y avait qu'un seul match sur la ligne - sinon, elle n'obtiendrait toujours que le dernier.

Vous pouvez voir ce pour une description de la façon dont cela fonctionne, mais cela imprimer sur une ligne séparée seulement le match autant de fois qu'il se produit sur une ligne.

Vous pouvez utiliser la même stratégie pour obtenir la [num]e occurrence sur une ligne. Par exemple, si vous souhaitez imprimer la correspondance CONFIG uniquement s'il s'agit du troisième tel sur une ligne:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

... bien que cela suppose que les CONFIGchaînes sont séparées par au moins un caractère non alphanumérique pour chaque occurrence.

Je suppose - pour la chose numérique - cela fonctionnerait également:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

... avec la même mise en garde qu'auparavant pour la main droite \n. Celui-ci serait même plus rapide que le premier, mais ne peut s'appliquer de manière générale, évidemment.

Pour la chose CONFIG, vous pouvez utiliser la P;...;Dboucle ci-dessus avec votre modèle, ou vous pouvez faire:

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

... qui est juste un peu plus impliqué et fonctionne en ordonnant correctement sedla priorité de référence. Il isole également toutes les correspondances CONFIG sur une ligne en une seule fois - bien qu'il fasse la même hypothèse qu'auparavant - que chaque correspondance CONFIG sera séparée par au moins un caractère non alphanumérique. Avec GNU, sedvous pouvez l'écrire:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
mikeserv
la source