Comment faire correspondre toutes les occurrences d'une expression régulière

586

Existe-t-il un moyen rapide de trouver chaque correspondance d'une expression régulière dans Ruby? J'ai parcouru l'objet Regex dans la Ruby STL et cherché sur Google en vain.

Chris Bunch
la source
3
J'ai lu ceci est comment puis-je rechercher une chaîne pour tous les modèles d'expression
régulière

Réponses:

821

L'utilisation scandevrait faire l'affaire:

string.scan(/regex/)
Jean
la source
9
Mais qu'en est-il de cette affaire? "Match me!". scan (/.../) = ["mat", "ch" "me!" ], mais toutes les occurrences de /.../ seraient ["mat", "atc", "tch", "ch", ...]
Michael Dickens
13
Ce ne serait pas le cas. /.../ est une expression rationnelle gourmande normale. Il ne reviendra pas sur le contenu correspondant. vous pouvez essayer d'utiliser une expression rationnelle paresseuse, mais même cela ne suffira probablement pas. jetez un oeil à la doc regexp ruby-doc.org/core-1.9.3/Regexp.html pour exprimer correctement votre regexp :)
Jean
49
cela ressemble à un Ruby WTF ... pourquoi est-ce sur String au lieu de Regexp avec les autres trucs regexp? Il n'est même pas mentionné nulle part sur les documents pour Regexp
Anentropic
9
Je suppose que c'est parce qu'il est défini et appelé sur String et non sur Regex ... Mais cela a du sens. Vous pouvez écrire une expression régulière pour capturer toutes les correspondances à l'aide de la correspondance Regex # et parcourir les groupes capturés. Ici, vous écrivez une fonction de correspondance partielle et souhaitez qu'elle soit appliquée plusieurs fois sur une chaîne donnée, ce n'est pas la responsabilité de Regexp. Je vous suggère de vérifier l'implémentation du scan pour une meilleure compréhension: ruby-doc.org/core-1.9.3/String.html#method-i-scan
Jean
9
@MichaelDickens: Dans ce cas, vous pouvez utiliser /(?=(...))/.
Konrad Borowski
67

Pour trouver toutes les chaînes correspondantes, utilisez la scanméthode String .

str = "A 54mpl3 string w1th 7 numb3rs scatter36 ar0und"
str.scan(/\d+/)
#=> ["54", "3", "1", "7", "3", "36", "0"]

Si vous le souhaitez, MatchDataqui est le type de l'objet renvoyé par la matchméthode Regexp , utilisez:

str.to_enum(:scan, /\d+/).map { Regexp.last_match }
#=> [#<MatchData "54">, #<MatchData "3">, #<MatchData "1">, #<MatchData "7">, #<MatchData "3">, #<MatchData "36">, #<MatchData "0">]

L'avantage d'utiliser MatchDataest que vous pouvez utiliser des méthodes telles que offset:

match_datas = str.to_enum(:scan, /\d+/).map { Regexp.last_match }
match_datas[0].offset(0)
#=> [2, 4]
match_datas[1].offset(0)
#=> [7, 8]

Consultez ces questions si vous souhaitez en savoir plus:

La lecture sur les variables spéciales $&, $', $1, $2Ruby sera utile aussi.

sudo bangbang
la source
12

si vous avez une expression rationnelle avec des groupes:

str="A 54mpl3 string w1th 7 numbers scatter3r ar0und"
re=/(\d+)[m-t]/

vous pouvez utiliser la scanméthode de String pour trouver des groupes correspondants:

str.scan re
#> [["54"], ["1"], ["3"]]

Pour trouver le motif correspondant:

str.to_enum(:scan,re).map {$&}
#> ["54m", "1t", "3r"]
MVP
la source
str.scan(/\d+[m-t]/) # => ["54m", "1t", "3r"]est plus idiomatique questr.to_enum(:scan,re).map {$&}
le Tin Man
Vous avez peut-être mal compris. L'expression régulière de l'exemple d'un utilisateur auquel j'ai répondu était: /(\d+)[m-t]/ne pas /\d+[m-t]/écrire: re = /(\d+)[m-t]/; str.scan(re)c'est la même chose str.scan(/(\d+)[mt]/)mais j'obtiens #> [["" 54 "], [" 1 "], [" 3 "]]et non "54m", "1t", "3r"]La question était: si j'ai une expression régulière avec un groupe et que je veux capturer tous les modèles sans changer le régulier expression (quitter le groupe), comment faire? En ce sens, une solution possible, quoique un peu cryptique et difficile à lire, était:str.to_enum(:scan,re).map {$&}
MVP
-1

Vous pouvez utiliser string.scan(your_regex).flatten. Si votre expression régulière contient des groupes, elle reviendra dans un seul tableau simple.

string = "A 54mpl3 string w1th 7 numbers scatter3r ar0und"
your_regex = /(\d+)[m-t]/
string.scan(your_regex).flatten
=> ["54", "1", "3"]

Regex peut également être un groupe nommé.

string = 'group_photo.jpg'
regex = /\A(?<name>.*)\.(?<ext>.*)\z/
string.scan(regex).flatten

Vous pouvez également utiliser gsub, c'est juste une autre façon si vous voulez MatchData.

str.gsub(/\d/).map{ Regexp.last_match }
Datt
la source
Supprimez le regroupement your_regex = /(\d+)[m-t]/et vous n'aurez pas besoin de l'utiliser flatten. Votre dernier exemple utilise last_matchqui, dans ce cas, est probablement sûr, mais est global et pourrait éventuellement être écrasé si une expression rationnelle était mise en correspondance avant l'appel last_match. Au lieu de cela, il est probablement plus sûr à utiliser string.match(regex).captures # => ["group_photo", "jpg"]ou string.scan(/\d+/) # => ["54", "3", "1", "7", "3", "0"]comme indiqué dans d'autres réponses, selon le modèle et les besoins.
The Tin Man