كيفية استخدام sed لاستبدال التواجد الأول فقط في الملف؟

StackOverflow https://stackoverflow.com/questions/148451

سؤال

أرغب في تحديث عدد كبير من ملفات مصدر C++ بتوجيه تضمين إضافي قبل أي #includes موجود.بالنسبة لهذا النوع من المهام، عادةً ما أستخدم برنامج نصي bash صغيرًا مع sed لإعادة كتابة الملف.

كيف يمكنني الحصول على sed لاستبدال التواجد الأول لسلسلة في ملف بدلاً من استبدال كل تكرار؟

إذا كنت تستخدم

sed s/#include/#include "newfile.h"\n#include/

فهو يستبدل كل #يشمل.

نرحب أيضًا بالاقتراحات البديلة لتحقيق نفس الشيء.

هل كانت مفيدة؟

المحلول

 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

أو إذا كنت تفضل: ملحوظة المحرر:يعمل مع جنو sed فقط.

sed '0,/RE/s//to_that/' file 

مصدر

نصائح أخرى

اكتب نصًا sed يستبدل فقط أول ظهور لـ "Apple" بـ "Banana"

إدخال المثال:انتاج:

     Apple       Banana
     Orange      Orange
     Apple       Apple

هذا هو البرنامج النصي البسيط: ملحوظة المحرر:يعمل مع جنو sed فقط.

sed '0,/Apple/{s/Apple/Banana/}' filename
sed '0,/pattern/s/pattern/replacement/' filename

لقد نجح هذا بالنسبة لي.

مثال

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

ملحوظة المحرر:كلاهما يعمل مع جنو sed فقط.

ان ملخص من العديد من المفيد الإجابات الموجودة, ، مكملة ب تفسيرات:

تستخدم الأمثلة هنا حالة استخدام مبسطة:استبدل كلمة "foo" بكلمة "bar" في السطر المطابق الأول فقط.
بسبب استخدام سلاسل ANSI C المقتبسة ($'...') لتوفير خطوط إدخال العينة، bash, ksh, ، أو zsh يفترض كالصدفة.


جنو sed فقط:

إجابة بن هوفشتاين يوضح لنا أن GNU يوفر امتداد إلى مواصفات بوسيكس ل sed يسمح بنموذج العنوانين التالي: 0,/re/ (re يمثل تعبيرًا عاديًا تعسفيًا هنا).

0,/re/ يسمح للتعبير العادي مباراة على السطر الأول أيضا.بعبارة أخرى:سيؤدي هذا العنوان إلى إنشاء نطاق من السطر الأول إلى السطر المطابق ويتضمنه re - سواء re يحدث في السطر الأول أو في أي سطر لاحق.

  • قارن هذا مع النموذج المتوافق مع POSIX 1,/re/, ، مما يؤدي إلى إنشاء نطاق يطابق من السطر الأول إلى السطر المطابق ويتضمنه re على تالي خطوط؛بعبارة أخرى:هذا لن يكتشف التواجد الأول لـ re تطابق إذا حدث ذلك على الأول خط و أيضا يمنع استخدام الاختزال // لإعادة استخدام التعبير العادي الأكثر استخدامًا (انظر النقطة التالية).[1]

إذا قمت بالجمع بين أ 0,/re/ عنوان مع s/.../.../ (استبدال) المكالمة التي تستخدم نفس التعبير العادي، فإن الأمر الخاص بك سوف يؤدي بشكل فعال فقط الاستبدال على أولاً الخط الذي يطابق re.
sed يوفر مريحة اختصار لإعادة استخدام التعبير العادي المطبق مؤخرًا:ان فارغ زوج محدد, //.

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

ميزات POSIX فقط sed مثل بي إس دي (ماك) sed (ستعمل أيضًا مع جنو sed):

منذ 0,/re/ لا يمكن استخدامها والنموذج 1,/re/ لن تكتشف re إذا حدث ذلك في السطر الأول (انظر أعلاه)، مطلوب معالجة خاصة للخط الأول.

إجابة MikhailVS يذكر هذه التقنية، ووضعها في مثال ملموس هنا:

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

ملحوظة:

  • التعبير العادي الفارغ // يتم استخدام الاختصار مرتين هنا:مرة واحدة لنقطة نهاية النطاق، ومرة ​​واحدة في s يتصل؛في كلتا الحالتين، regex foo يتم إعادة استخدامه ضمنيًا، مما يسمح لنا بعدم الاضطرار إلى تكراره، مما يجعل التعليمات البرمجية أقصر وأكثر قابلية للصيانة.

  • بوسيكس sed يحتاج إلى أسطر جديدة فعلية بعد وظائف معينة، مثل بعد اسم التسمية أو حتى حذفها، كما هو الحال مع t هنا؛تقسيم النص بشكل استراتيجي إلى عدة -e الخيارات هي بديل لاستخدام الخطوط الجديدة الفعلية:نهاية كل منهما -e قطعة البرنامج النصي حيث يحتاج السطر الجديد عادةً إلى الانتقال.

1 s/foo/bar/ يستبدل foo على السطر الأول فقط، إذا وجدت هناك.لو ذلك، t الفروع حتى نهاية البرنامج النصي (يتخطى الأوامر المتبقية على السطر).(ال t تتفرع الدالة إلى التسمية فقط إذا كانت الأحدث s تم إجراء المكالمة استبدال فعلي؛في حالة عدم وجود تسمية، كما هو الحال هنا، يتم تفرع نهاية البرنامج النصي إلى).

عندما يحدث ذلك، عنوان النطاق 1,//, ، والذي عادةً ما يجد التواجد الأول ابتداء من السطر 2, ، سوف لا المباراة، وسوف النطاق لا تتم معالجتها، لأنه يتم تقييم العنوان عندما يكون السطر الحالي موجودًا بالفعل 2.

وعلى العكس من ذلك، إذا لم يكن هناك تطابق في السطر الأول، 1,// سوف سيتم إدخالها، وسوف تجد المباراة الأولى الحقيقية.

التأثير الصافي هو نفسه كما هو الحال مع GNU sed0,/re/:يتم استبدال التواجد الأول فقط، سواء حدث في السطر الأول أو أي سطر آخر.


النهج غير المدى

إجابة بوتونج يوضح حلقة التقنيات الذي - التي تجاوز الحاجة إلى نطاق;منذ أن يستخدم جنو sed بناء الجملة، وهنا معادلات متوافقة مع POSIX:

تقنية الحلقة 1:في المباراة الأولى، قم بإجراء التبديل، ثم أدخل حلقة تطبع الأسطر المتبقية كما هي:

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

تقنية الحلقة 2، ل ملفات صغيرة فقط: اقرأ المدخلات بالكامل في الذاكرة، ثم قم بإجراء استبدال واحد عليها.

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

[1] 1.61803 يقدم أمثلة على ما يحدث مع 1,/re/, ، مع وبدون لاحقة s//:
- sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' عائدات $'1bar\n2bar';أي.، كلاهما تم تحديث الخطوط، لأن رقم السطر 1 يطابق السطر الأول، و regex /foo/ - نهاية النطاق - ثم يتم البحث فقط عن البدء في التالي خط.لذلك، كلاهما يتم تحديد الخطوط في هذه الحالة، و s/foo/bar/ يتم إجراء الاستبدال على كل منهما.
- sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' فشل:مع sed: first RE may not be empty (BSD/ماك) و sed: -e expression #1, char 0: no previous regular expression (GNU)، لأنه في الوقت الذي تتم فيه معالجة السطر الأول (بسبب رقم السطر 1 بدء النطاق)، لم يتم تطبيق أي تعبير عادي حتى الآن، لذلك // لا يشير إلى أي شيء.
باستثناء جنو sedخاص 0,/re/ بناء الجملة، أي النطاق الذي يبدأ بـ a رقم السطر يمنع بشكل فعال استخدام //.

يمكنك استخدام awk لفعل شيء مماثل ..

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

توضيح:

/#include/ && !done

يتم تشغيل بيان الإجراء بين {} عندما يتطابق السطر مع "#include" ولم نقم بمعالجته بالفعل.

{print "#include \"newfile.h\""; done=1;}

يؤدي هذا إلى طباعة #include "newfile.h"، ونحن بحاجة للهروب من علامات الاقتباس.ثم قمنا بتعيين المتغير Done على 1، حتى لا نضيف المزيد من التضمينات.

1;

هذا يعني "طباعة السطر" - الإجراء الفارغ هو طباعة $0 افتراضيًا، مما يؤدي إلى طباعة السطر بالكامل.بطانة واحدة وأسهل للفهم من sed IMO :-)

مجموعة شاملة تمامًا من الإجابات على الأسئلة الشائعة حول Linuxtopia sed.كما يسلط الضوء أيضًا على أن بعض الإجابات التي قدمها الأشخاص لن تعمل مع إصدار sed بخلاف GNU، على سبيل المثال

sed '0,/RE/s//to_that/' file

في إصدار غير GNU يجب أن يكون

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

ومع ذلك، لن يعمل هذا الإصدار مع gnu sed.

إليك الإصدار الذي يعمل مع كليهما:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

السابق:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename

ما عليك سوى إضافة رقم التكرار في النهاية:

sed s/#include/#include "newfile.h"\n#include/1
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

كيف يعمل هذا البرنامج النصي:للخطوط بين 1 والأول #include (بعد السطر 1)، إذا كان السطر يبدأ بـ #include, ، ثم قم بإلحاق السطر المحدد مسبقًا.

ومع ذلك، إذا كان الأول #include في السطر 1، ثم السطر 1 والسطر الذي يليه #include سيكون الخط مُسبقًا.إذا كنت تستخدم جنو sed, ، وله امتداد حيث 0,/^#include/ (بدلاً من 1,) سوف تفعل الشيء الصحيح.

حل ممكن:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :
    n
    b

توضيح:

  • نقرأ السطور حتى نجد #التضمين، نطبع هذه السطور ثم نبدأ دورة جديدة
  • أدخل سطر التضمين الجديد
  • أدخل حلقة تقرأ الأسطر فقط (افتراضيًا سيطبع sed هذه الأسطر أيضًا)، ولن نعود إلى الجزء الأول من البرنامج النصي من هنا

أعلم أن هذا منشور قديم ولكن كان لدي حل اعتدت استخدامه:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

استخدم بشكل أساسي grep للعثور على التواجد الأول والتوقف عند هذا الحد.قم أيضًا بطباعة رقم السطر أي 5: السطر.قم بتوصيل ذلك إلى sed وقم بإزالة:وأي شيء بعد ذلك يتبقى لك رقم السطر.قم بتوجيه ذلك إلى sed الذي يضيف s/.*/replace إلى النهاية مما يعطي البرنامج النصي المكون من سطر واحد والذي يتم نقله إلى sed الأخير لتشغيله كبرنامج نصي في الملف.

لذا، إذا كان regex = #include and استبدال = blah وكان أول ظهور grep يجده في السطر 5، فإن البيانات المنقولة إلى آخر sed ستكون 5s/.*/blah/.

إذا جاء أي شخص إلى هنا ليحل محل حرف لأول ظهور في جميع السطور (مثلي)، فاستخدم هذا:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

عن طريق تغيير 1 إلى 2 على سبيل المثال، يمكنك استبدال كل الحروف الثانية فقط بدلاً من ذلك.

سأفعل هذا باستخدام البرنامج النصي awk:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

ثم قم بتشغيله باستخدام awk:

awk -f awkscript headerfile.h > headerfilenew.h

قد يكون قذرا، أنا جديد على هذا.

كاقتراح بديل قد ترغب في إلقاء نظرة على ed يأمر.

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF

لقد تمكنت أخيرًا من العمل في برنامج Bash النصي المستخدم لإدراج طابع زمني فريد في كل عنصر في موجز RSS:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

يغير التواجد الأول فقط.

${nowms} هو الوقت بالمللي ثانية الذي يحدده برنامج Perl النصي، $counter هو عداد يستخدم للتحكم في الحلقة داخل البرنامج النصي، \ يسمح بمواصلة الأمر في السطر التالي.

تتم قراءة الملف وإعادة توجيه stdout إلى ملف عمل.

بالطريقة التي أفهمها، 1,/====RSSpermalink====/ يخبر sed متى يتوقف عن طريق تحديد نطاق، وبعد ذلك s/====RSSpermalink====/${nowms}/ هو الأمر sed المألوف لاستبدال السلسلة الأولى بالسلسلة الثانية.

في حالتي، أضع الأمر بين علامتي اقتباس مزدوجتين لأنني أستخدمه في برنامج نصي Bash يحتوي على متغيرات.

استخدام فري بي إس دي ed وتجنب edخطأ "لا يوجد تطابق" في حالة عدم وجوده include بيان في ملف لتتم معالجته:

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF

قد يناسبك هذا (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

أو إذا كانت الذاكرة ليست مشكلة:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...

مع جنو سيد -z الخيار الذي يمكنك من خلاله معالجة الملف بأكمله كما لو كان سطرًا واحدًا فقط.بهذه الطريقة أ s/…/…/ سيستبدل فقط المطابقة الأولى في الملف بأكمله.يتذكر: s/…/…/ يستبدل فقط المطابقة الأولى في كل سطر، ولكن مع -z خيار sed يعامل الملف بأكمله كسطر واحد.

sed -z 's/#include/#include "newfile.h"\n#include'

في الحالة العامة، يجب عليك إعادة كتابة تعبير sed الخاص بك نظرًا لأن مساحة النمط تحتوي الآن على الملف بأكمله بدلاً من سطر واحد فقط.بعض الأمثلة:

  • s/text.*// يمكن إعادة كتابتها كما s/text[^\n]*//. [^\n] يطابق كل شيء يستثني حرف السطر الجديد. [^\n]* سوف تتطابق مع جميع الرموز بعد text حتى يتم الوصول إلى السطر الجديد.
  • s/^text// يمكن إعادة كتابتها كما s/(^|\n)text//.
  • s/text$// يمكن إعادة كتابتها كما s/text(\n|$)//.

يقوم الأمر التالي بإزالة التواجد الأول لسلسلة داخل ملف.فهو يزيل السطر الفارغ أيضًا.يتم تقديمه في ملف xml، ولكنه سيعمل مع أي ملف.

يكون هذا مفيدًا إذا كنت تعمل مع ملفات xml وتريد إزالة علامة.في هذا المثال، تتم إزالة التواجد الأول للعلامة "isTag".

يأمر:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

الملف المصدر (source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ملف النتيجة (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ملاحظة:لم يعمل معي على Solaris SunOS 5.10 (قديم جدًا)، لكنه يعمل على Linux 2.6، الإصدار 4.1.5

لا شيء جديد ولكن ربما إجابة أكثر تحديدًا: sed -rn '0,/foo(bar).*/ s%%\1%p'

مثال: xwininfo -name unity-launcher ينتج مخرجات مثل:

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

استخراج معرف النافذة مع xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' ينتج عنه:

0x2200003

POSIXly (صالح أيضًا في sed)، فقط واحد التعبير العادي المستخدم، يحتاج إلى ذاكرة لسطر واحد فقط (كالمعتاد):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

شرح:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top