Gsub が同じコードを実行して異なる結果を生成する

これはもしかしたら Stack Overflow の質問で、gsub が何をするのか理解していないのかもしれませんが、これは奇妙な Ruby の動作のように見えます。もしかして 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

ここで起こっていたことは、ループの 2 回目の反復で tpost.raw が、前の反復での最後の post.raw の値を取得していたことです。

自分が狂っているのかと思いました。しかし、私はこれを見たことがあります。

これはより単純な奇妙な現象です。このバージョンでは、最初の実行では gsub が機能しませんが、2 回目の実行では機能します。

[1] pry(main)> s=%(<p>Android では、右上隅の三点リーダーアイコンをクリックし、ポップアップメニューから「Relations」を選択してください。この機能は昨日まで私では動作しませんでした。おそらく 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 では、右上隅の三点リーダーアイコンをクリックし、ポップアップメニューから「Relations」を選択してください。この機能は昨日まで私では動作しませんでした。おそらく 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 では、右上隅の三点リーダーアイコンをクリックし、ポップアップメニューから「Relations」を選択してください。この機能は昨日まで私では動作しませんでした。おそらく 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 では、右上隅の三点リーダーアイコンをクリックし、ポップアップメニューから「Relations」を選択してください。この機能は昨日まで私では動作しませんでした。おそらく 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 は過去の正規表現処理から残った値になってしまいます(これが、2 回目の試みが機能する理由です。最初の試みからの $1 を使用しているためです)。

この問題を解決する方法はいくつかあります。gsub には多様な機能があります。

私のおすすめは、gsub にブロックを渡す方法です。ブロックは正規表現の一致の後に評価されるため、$1 が期待通りに機能します:

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

あるいは、gsub の「バックリファレンス」機能を使用することもできます。この構文は好みではありませんが、機能します。"replacement#{$1}" の代わりに 'replacement\1'(または \2\3 など)を使用します。

s.gsub('original(value)', 'replacement\1')
「いいね!」 5

OMG、ありがとうございます。自分が気が狂うかと思いました。

素晴らしいですね。

あのバックスラッシュについてはいつも疑問に思っていました。5 年越しにやっと理解できました。

これ、完全に Stack Exchange の質問ですね。:man_shrugging:

心から感謝申し上げます。

「いいね!」 2

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.