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
a
contre .*
et 2
contre [0-9]*
; 1
correspond à [0-9]
.
- Match
a1
contre .*
et la chaîne vide contre [0-9]*
; 2
correspond à [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 a1
contre .*
, 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 \n
en charge pour signifier une nouvelle ligne dans le s
texte 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 T
commande dit d'abandonner si la s
commande 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