كيفية استخدام sed لاستبدال التواجد الأول فقط في الملف؟
-
02-07-2019 - |
سؤال
أرغب في تحديث عدد كبير من ملفات مصدر 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
يتصل؛في كلتا الحالتين، regexfoo
يتم إعادة استخدامه ضمنيًا، مما يسمح لنا بعدم الاضطرار إلى تكراره، مما يجعل التعليمات البرمجية أقصر وأكثر قابلية للصيانة.بوسيكس
sed
يحتاج إلى أسطر جديدة فعلية بعد وظائف معينة، مثل بعد اسم التسمية أو حتى حذفها، كما هو الحال معt
هنا؛تقسيم النص بشكل استراتيجي إلى عدة-e
الخيارات هي بديل لاستخدام الخطوط الجديدة الفعلية:نهاية كل منهما-e
قطعة البرنامج النصي حيث يحتاج السطر الجديد عادةً إلى الانتقال.
1 s/foo/bar/
يستبدل foo
على السطر الأول فقط، إذا وجدت هناك.لو ذلك، t
الفروع حتى نهاية البرنامج النصي (يتخطى الأوامر المتبقية على السطر).(ال t
تتفرع الدالة إلى التسمية فقط إذا كانت الأحدث s
تم إجراء المكالمة استبدال فعلي؛في حالة عدم وجود تسمية، كما هو الحال هنا، يتم تفرع نهاية البرنامج النصي إلى).
عندما يحدث ذلك، عنوان النطاق 1,//
, ، والذي عادةً ما يجد التواجد الأول ابتداء من السطر 2, ، سوف لا المباراة، وسوف النطاق لا تتم معالجتها، لأنه يتم تقييم العنوان عندما يكون السطر الحالي موجودًا بالفعل 2
.
وعلى العكس من ذلك، إذا لم يكن هناك تطابق في السطر الأول، 1,//
سوف سيتم إدخالها، وسوف تجد المباراة الأولى الحقيقية.
التأثير الصافي هو نفسه كما هو الحال مع GNU sed
'س 0,/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.