Gsub produit des résultats différents en exécutant le même code

Peut-être que c’est une question pour Stack Overflow et que je ne comprends pas ce que fait gsub, mais cela ressemble à un comportement Ruby bizarre qui me fait me demander si cela pourrait être lié à l’image Ruby. Je obtiens les mêmes résultats dans irb sur ma machine locale.

Je pensais que cela pouvait être un comportement de dup que je ne comprenais pas, mais je peux reproduire le comportement si je définis la chaîne deux fois. La première fois, gsub échoue à insérer l’URL, mais les exécutions suivantes des mêmes données l’incluent comme prévu.

[1] pry(main)> save=%(<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est encore en test A/B. <UPL-IMAGE-PREVIEW url="https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png">[upl-image-preview url=https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png]</UPL-IMAGE-PREVIEW></p>
[1] pry(main)* </r>
[1] pry(main)* )
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est encore en test A/B. <UPL-IMAGE-PREVIEW url=\"https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png\">[upl-image-preview url=https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png]</UPL-IMAGE-PREVIEW></p>\n</r>\n"
[2] pry(main)> s=save.dup
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est encore en test A/B. <UPL-IMAGE-PREVIEW url=\"https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png\">[upl-image-preview url=https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png]</UPL-IMAGE-PREVIEW></p>\n</r>\n"
[3] pry(main)> s.gsub!(/<UPL-IMAGE-PREVIEW url="(.+?)">.+?</UPL-IMAGE-PREVIEW>/i,"\nIMAGEISHERE\n#{$1}\n")
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est encore en test A/B. \nIMAGEISHERE\n\n</p>\n</r>\n"
[4] pry(main)> s=save.dup
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est encore en test A/B. <UPL-IMAGE-PREVIEW url=\"https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png\">[upl-image-preview url=https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png]</UPL-IMAGE-PREVIEW></p>\n</r>\n"
[5] pry(main)> s.gsub!(/<UPL-IMAGE-PREVIEW url="(.+?)">.+?</UPL-IMAGE-PREVIEW>/i,"\nIMAGEISHERE\n#{$1}\n")
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est encore en test A/B. \nIMAGEISHERE\nhttps://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png\n</p>\n</r>\n"

Les exécutions suivantes de

s=save.dup
s.gsub!(/<UPL-IMAGE-PREVIEW url="(.+?)">.+?</UPL-IMAGE-PREVIEW>/i,"\nIMAGEISHERE\n#{$1}\n")

produisent le remplacement de l’URL comme je m’y attendais.

J’ai rencontré un problème similaire l’autre jour que j’ai décrit dans ce post, mais dans une édition précédente. Ce code était

def fix_slack_posts
  SiteSetting.min_post_length = 2
  reg=/(\\*\\*)(This topic was automatically generated from Slack. You can find the original thread \\[here\\].+?\\))(\\*\\*\\.)?\\s*?([a-zA-Z, ()]* : )(.*)/m
  preg = /([a-zA-Z, ()]+? : )(.*)/m
  topic_posts = Post.where("raw like '**This topic was automatically%'")
  topic_posts.each do |tpost|
    begin
      tpost.raw.gsub!(reg,"#{$5}\n\n#{$2}.")
      tpost.save!
      tpost.rebake!
    rescue
      puts "Can't update topic post #{tpost.raw}"
    end
    posts = Post.where(topic_id: tpost.topic_id).where("post_number > 1")
    posts.each do |post|
      if post.raw.gsub(preg,"#{$2}").length>=10
        begin
          post.raw.gsub!(preg,"#{$2}")
          post.save!
          post.rebake!
        rescue
          puts "#{post.id}--cannot save #{post.raw}. "
        end
      end
    end
  end
  SiteSetting.min_post_length = 10
end

Et ce qui se passait, c’est que tpost.raw lors de la deuxième itération de la boucle prenait la valeur du dernier post.raw de l’itération précédente.

Je pensais devenir fou, mais j’ai vu cela

Voici une bizarrerie plus simple. Cette version ne fait pas fonctionner gsub au premier essai, mais cela fonctionne au second :

[1] pry(main)> s=%(<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est toujours en test A/B. <UPL-IMAGE-PREVIEW url="https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png">[upl-image-preview url=https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png]</UPL-IMAGE-PREVIEW></p>
[1] pry(main)* </r>
[1] pry(main)* )
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est toujours en test A/B. <UPL-IMAGE-PREVIEW url=\"https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png\">[upl-image-preview url=https://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png]</UPL-IMAGE-PREVIEW></p>\n</r>\n"
[2] pry(main)> s.gsub(/<UPL-IMAGE-PREVIEW url="(.+?)">.+?</UPL-IMAGE-PREVIEW>/mi,"\nIMAGEISHERE\n#{$1}\n")
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est toujours en test A/B. \nIMAGEISHERE\n\n</p>\n</r>\n"
[3] pry(main)> s.gsub(/<UPL-IMAGE-PREVIEW url="(.+?)">.+?</UPL-IMAGE-PREVIEW>/mi,"\nIMAGEISHERE\n#{$1}\n")
=> "<p>Sur Android, cliquez sur l'icône à trois points en haut à droite et sélectionnez Relations dans le menu contextuel. Cette fonction ne fonctionnait pas pour moi jusqu'à hier, donc peut-être que sur Android, elle est toujours en test A/B. \nIMAGEISHERE\nhttps://somehost.s3.eu-central-1.amazonaws.com/2021-08-29/1630236738-85280-screen-shot-2021-08-29-at-83023-pm.png\n</p>\n</r>\n"

Les variables comme $1 sont remplies après l’exécution d’une correspondance de regex. Simplifions un peu votre exemple :

s.gsub('original(value)', "replacement#{$1}")

L’expression "replacement#{$1}" est évaluée avant que la fonction gsub ne soit appelée. Ainsi, $1 contiendra la valeur résiduelle d’une tâche de regex précédente. (C’est pourquoi votre deuxième tentative fonctionne : elle récupère $1 de la première tentative)

Plusieurs options permettent de résoudre ce problème. gsub offre de nombreuses fonctionnalités différentes.

Ma préférence va à la transmission d’un bloc à gsub. Le bloc est évalué après la correspondance de la regex, de sorte que $1 fonctionne comme prévu :

s.gsub('original(value)') { |match| "replacement#{$1}" }

Vous pouvez également utiliser la fonctionnalité de « référence arrière » de gsub. Je ne suis pas fan de cette syntaxe, mais elle fonctionne. Au lieu d’utiliser "replacement#{$1}", utilisez 'replacement\\1' (ou \\2, \\3, etc.)

s.gsub('original(value)', 'replacement\\1')

OMG. Merci. J’ai cru que je devenais fou.

Brillant.

Je me suis toujours demandé à quoi servaient ces barres obliques. Cinq ans plus tard, je comprends.

C’était tout à fait une question pour Stack Exchange. :man_shrugging:

Un million de mercis.