Gsub produce risultati diversi eseguendo lo stesso codice

Forse questa è una domanda per Stack Overflow e non capisco cosa faccia gsub, ma questo sembra un comportamento bizzarro di Ruby che mi fa chiedersi se possa in qualche modo dipendere dall’immagine di Ruby. Ottengo gli stessi risultati in irb sulla mia macchina locale.

Pensavo potesse essere un comportamento di dup che non comprendevo, ma riesco a replicare il comportamento definendo la stringa due volte. La prima volta che gsub non riesce a inserire l’URL, ma nelle esecuzioni successive con gli stessi dati lo include come previsto.

[1] pry(main)> save=%(<p>On Android, click the three-dot icon in the upper right corner and select Relations from the popup menu. This function wasn't working for me until yesterday, so perhaps on Android, it's still in A/B testing. <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>On Android, click the three-dot icon in the upper right corner and select Relations from the popup menu. This function wasn't working for me until yesterday, so perhaps on Android, it's still in A/B testing. <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>On Android, click the three-dot icon in the upper right corner and select Relations from the popup menu. This function wasn't working for me until yesterday, so perhaps on Android, it's still in A/B testing. <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>On Android, click the three-dot icon in the upper right corner and select Relations from the popup menu. This function wasn't working for me until yesterday, so perhaps on Android, it's still in A/B testing. \nIMAGEISHERE\n\n</p>\n</r>\n"
[4] pry(main)> s=save.dup
=> "<p>On Android, click the three-dot icon in the upper right corner and select Relations from the popup menu. This function wasn't working for me until yesterday, so perhaps on Android, it's still in A/B testing. <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>On Android, click the three-dot icon in the upper right corner and select Relations from the popup menu. This function wasn't working for me until yesterday, so perhaps on Android, it's still in A/B testing. \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"

Le esecuzioni successive di

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

producono la sostituzione dell’URL come previsto.

funzionano come previsto.

L’altro giorno ho avuto un problema simile che ho descritto in questo post, ma in una modifica precedente. Quel codice era

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

E ciò che stava accadendo era che tpost.raw nella seconda iterazione del ciclo assumeva il valore dell’ultimo post.raw dell’iterazione precedente.

Pensavo di impazzire, ma ho visto questo

Ecco una stranezza più semplice. Questa versione non esegue gsub al primo tentativo, ma funziona al secondo:

[1] pry(main)> s=%(<p>Su Android, clicca sull'icona con i tre puntini in alto a destra e seleziona Relazioni dal menu a comparsa. Questa funzione non funzionava per me fino a ieri, quindi forse su Android è ancora in fase di 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>Su Android, clicca sull'icona con i tre puntini in alto a destra e seleziona Relazioni dal menu a comparsa. Questa funzione non funzionava per me fino a ieri, quindi forse su Android è ancora in fase di 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>Su Android, clicca sull'icona con i tre puntini in alto a destra e seleziona Relazioni dal menu a comparsa. Questa funzione non funzionava per me fino a ieri, quindi forse su Android è ancora in fase di 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>Su Android, clicca sull'icona con i tre puntini in alto a destra e seleziona Relazioni dal menu a comparsa. Questa funzione non funzionava per me fino a ieri, quindi forse su Android è ancora in fase di 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"


Variabili come $1 vengono popolate dopo l’esecuzione di una corrispondenza regex. Semplificando un po’ il tuo esempio:

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

L’espressione "replacement#{$1}" viene valutata prima che venga chiamata la funzione gsub. Quindi $1 conterrà il valore residuo di un’attività regex precedente. (È per questo che il tuo secondo tentativo funziona: prende $1 dal primo tentativo)

Ci sono alcune opzioni per risolvere questo problema. gsub offre molte funzionalità diverse.

La mia preferenza è passare un blocco a gsub. Il blocco viene valutato dopo che la corrispondenza regex è stata trovata, quindi $1 funzionerà come ti aspetti:

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

In alternativa, puoi utilizzare la funzionalità di “backreference” di gsub. Non sono un fan di questa sintassi, ma funziona. Invece di usare "replacement#{$1}", usa 'replacement\1' (o \2, \3, ecc.)

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

Mamma mia. Grazie. Pensavo di star impazzendo.

Brillante.

Mi sono sempre chiesto di quelle barre rovesciate. Cinque anni dopo, ho capito.

Questa era proprio una domanda per Stack Exchange. :man_shrugging:

Un milione di grazie.