Gsub ينتج نتائج مختلفة عند تشغيل نفس الكود

ربما هذا سؤال في StackOverflow ولا أفهم ما يفعله gsub، لكن هذا يبدو كسلوك غريب في Ruby أتساءل عما إذا كان قد يكون مرتبطًا بصورة Ruby. أحصل على نفس النتائج في irb على جهازي المحلي.

ظننت أن هذا قد يكون سلوكًا لـ dup لم أفهمه، لكنني أستطيع تكرار السلوك إذا عرفت السلسلة مرتين. في المرة الأولى يفشل gsub في إدراج الرابط، لكن التكرارات اللاحقة لنفس البيانات تتضمنه كما أتوقع.

[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 u
ntil 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-85
280-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-2
021-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-2
021-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")

تنتج استبدال الرابط كما أتوقع.

تعمل كما هو متوقع.

كان لدي مشكلة مشابهة اليوم السابق والتي وصفتها في هذا المنشور، ولكن في تعديل سابق. كان الكود هو

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>في نظام أندرويد، انقر فوق رمز النقاط الثلاث في الزاوية العلوية اليمنى وحدد "العلاقات" من القائمة المنبثقة. لم تكن هذه الوظيفة تعمل لدي حتى الأمس، لذا ربما لا تزال قيد اختبار 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>في نظام أندرويد، انقر فوق رمز النقاط الثلاث في الزاوية العلوية اليمنى وحدد "العلاقات" من القائمة المنبثقة. لم تكن هذه الوظيفة تعمل لدي حتى الأمس، لذا ربما لا تزال قيد اختبار 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>في نظام أندرويد، انقر فوق رمز النقاط الثلاث في الزاوية العلوية اليمنى وحدد "العلاقات" من القائمة المنبثقة. لم تكن هذه الوظيفة تعمل لدي حتى الأمس، لذا ربما لا تزال قيد اختبار 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>في نظام أندرويد، انقر فوق رمز النقاط الثلاث في الزاوية العلوية اليمنى وحدد "العلاقات" من القائمة المنبثقة. لم تكن هذه الوظيفة تعمل لدي حتى الأمس، لذا ربما لا تزال قيد اختبار 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 على وظائف متنوعة كثيرة.

تتمثل تفضيلي في تمرير كتلة (block) إلى gsub. يتم تقييم الكتلة بعد إجراء تطابق التعبير النمطي، لذا ستعمل $1 كما تتوقع:

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

أو يمكنك استخدام ميزة “المرجع الخلفي” (backreference) في gsub. لست معجبًا بهذا التركيب النحوي، لكنه يعمل. بدلاً من استخدام "replacement#{$1}"، استخدم 'replacement\1' (أو \2، أو \3، وهكذا).

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

يا إلهي، شكرًا لك. ظننت أنني أفقد عقلي.

رائع.

لطالما تساءلت عن تلك الشبكات المائلة. بعد خمس سنوات، فهمت الأمر.

كان هذا بالتأكيد سؤالًا على Stack Exchange. :man_shrugging:

شكرًا جزيلًا لك.