Gsub выдает разные результаты при запуске одного и того же кода

Возможно, это вопрос для Stack Overflow, и я не понимаю, что делает gsub, но это выглядит как странное поведение Ruby, и я задаюсь вопросом, не связано ли это somehow с образом Ruby. Однако я получаю те же результаты в irb на своей локальной машине.

Я думал, что это может быть какое-то поведение dup, которое я не понимаю, но я могу воспроизвести это поведение, даже если определить строку дважды. В первый раз gsub не вставляет URL, но при последующих запусках с теми же данными он добавляется, как я и ожидал.

[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"

Последующие запуски

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

дают замену URL так, как я и ожидал.

работает как ожидалось.

Недавно у меня была похожая проблема, которую я описал в этом посте, но в предыдущем редактировании. Этот код был следующим:

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

Что происходило, так это то, что tpost.raw на второй итерации цикла получал значение post.raw с последней итерации предыдущего цикла.

Я думал, что схожу с ума, но я видел это

Вот ещё одна странность, но более простая. В этой версии gsub не работает при первом запуске, но работает при втором:

[1] pry(main)> s=%(<p>На Android нажмите на значок с тремя точками в правом верхнем углу и выберите «Отношения» из всплывающего меню. Эта функция не работала у меня до вчерашнего дня, так что, возможно, на Android она всё ещё находится в 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>На Android нажмите на значок с тремя точками в правом верхнем углу и выберите «Отношения» из всплывающего меню. Эта функция не работала у меня до вчерашнего дня, так что, возможно, на Android она всё ещё находится в 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>На Android нажмите на значок с тремя точками в правом верхнем углу и выберите «Отношения» из всплывающего меню. Эта функция не работала у меня до вчерашнего дня, так что, возможно, на Android она всё ещё находится в 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>На Android нажмите на значок с тремя точками в правом верхнем углу и выберите «Отношения» из всплывающего меню. Эта функция не работала у меня до вчерашнего дня, так что, возможно, на Android она всё ещё находится в 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"


Переменные вроде $1 заполняются после выполнения сопоставления с регулярным выражением. Немного упростим ваш пример:

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

Выражение "replacement#{$1}" вычисляется до вызова функции gsub. Поэтому $1 будет содержать значение от какой-то предыдущей операции с регулярным выражением. (Именно поэтому ваша вторая попытка сработала — она использовала $1 из первой попытки).

Есть несколько способов решить эту проблему. У метода gsub много разных возможностей.

Мой предпочтительный вариант — передать в gsub блок. Блок выполняется после того, как регулярное выражение найдено, поэтому $1 будет работать так, как вы ожидаете:

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

Или можно использовать функцию «обратной ссылки» в gsub. Мне не нравится этот синтаксис, но он работает. Вместо "replacement#{$1}" используйте 'replacement\1' (или \2, \3 и т. д.).

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

О боже, спасибо. Я думал, что схожу с ума.

Гениально.

Я всегда задавался вопросом насчёт этих обратных слэшей. Пять лет спустя я наконец понял.

Это точно был вопрос на Stack Exchange. :man_shrugging:

Огромное спасибо.