ใช้ sed เพื่อค้นหาและแทนที่สตริงที่ซับซ้อน (ดีกว่าด้วย regex)


84

ฉันมีไฟล์ที่มีเนื้อหาดังต่อไปนี้:

<username><![CDATA[name]]></username>
<password><![CDATA[password]]></password>
<dbname><![CDATA[name]]></dbname>

และฉันต้องสร้างสคริปต์ที่เปลี่ยน "ชื่อ" ในบรรทัดแรกเป็น "บางอย่าง", "รหัสผ่าน" ในบรรทัดที่สองเป็น "someelse" และ "ชื่อ" ในบรรทัดที่สามเป็น "somethingdifferent" ฉันไม่สามารถพึ่งพาลำดับของสิ่งที่เกิดขึ้นเหล่านี้ในไฟล์ดังนั้นฉันจึงไม่สามารถแทนที่ "ชื่อ" ด้วย "บางอย่าง" เป็นครั้งแรกและเกิดขึ้นเป็นครั้งที่สองของ "ชื่อ" ด้วย "somethingdifferent" ฉันต้องค้นหาสตริงโดยรอบเพื่อให้แน่ใจว่าฉันค้นหาและแทนที่สิ่งที่ถูกต้อง

จนถึงตอนนี้ฉันได้ลองคำสั่งนี้เพื่อค้นหาและแทนที่เหตุการณ์ "ชื่อ" แรก:

sed -i "s/<username><![CDATA[name]]><\/username>/something/g" file.xml

อย่างไรก็ตามมันไม่ทำงานดังนั้นฉันจึงคิดว่าตัวละครบางตัวอาจต้องหลบหนี ฯลฯ

โดยหลักการแล้วฉันยินดีที่จะใช้ regex เพียงแค่จับคู่ "ชื่อผู้ใช้" สองครั้งและแทนที่ "ชื่อ" เท่านั้น บางอย่างเช่นนี้ แต่ด้วยsed:

<username>.+?(name).+?</username>

และแทนที่เนื้อหาในวงเล็บด้วย "บางอย่าง"

เป็นไปได้ไหม


2
เพิ่งทราบว่าโซลูชัน regexp แบบใดก็ตามที่มีการวางแผนไว้อย่างดีจะมีความเสี่ยงต่อการเปลี่ยนแปลงรูปแบบการป้อนข้อมูลเมื่อใดก็ได้ Regexps เป็นตัวเลือกที่ไม่ดีสำหรับการจัดการกับ XML, SGML หรือ derivates
CVn

ได้รับการอนุมัติ! พิจารณาใช้ XQuery ตัวอย่างเช่น: w3schools.com/xquery/default.asp นี่คือมาตรฐาน W3C สำหรับการดึงและจัดการเนื้อหา XML
lgeorget

คำตอบ:


157
sed -i -E "s/(<username>.+)name(.+<\/username>)/\1something\2/" file.xml

นี่คือสิ่งที่ฉันกำลังมองหา

คำอธิบาย:

  • วงเล็บในส่วนแรกกำหนดกลุ่ม (สตริงในความเป็นจริง) ที่สามารถนำกลับมาใช้ในส่วนที่สอง
  • \1, \2ฯลฯ ในส่วนที่สองเป็นการอ้างอิงถึงกลุ่ม i-th ที่ถูกจับในส่วนแรก (การกำหนดหมายเลขเริ่มต้นด้วย 1)
  • -Eเปิดใช้งานนิพจน์ทั่วไปแบบขยาย (จำเป็นสำหรับ+และการจัดกลุ่ม)

20
+1 สำหรับตัวเลือก -E
slackmart

4
(original name) + "-E"ที่จะออกจากที่อยู่เบื้องหลังแฟ้มสำรองข้อมูลที่มีชื่อ
Sarge Borsch

4
ใน OSX ฉันได้รับ 'sed: 1: "s / (<username>. +) ชื่อ (. + ... ": \ 1 ไม่ได้กำหนดใน RE' ฉันวางตัวอย่างที่แน่นอนจากคำถามนี้ลงในไฟล์แล้ว ฉันรันคำสั่งจากคำตอบนี้ในไฟล์นั้นอาจ OSX มีไวยากรณ์ที่แตกต่างกันหรือไม่
4153 deweydb

1
รุ่น gnu ของ sed รองรับพารามิเตอร์ "-E" แต่ไม่ใช่เป็นทางการ มันไม่ได้พูดถึงแม้แต่ใน manpage หากคุณต้องการใช้ Extended Regex คุณต้องใช้พารามิเตอร์ "-r" แทน
Ikem Krueger

3
@deweydb ตามคำตอบนี้คุณควรใช้\(และ\)แทนและ( )
Zhang Buzz

14
sed -e '/username/s/CDATA\[name\]/CDATA\[something\]/' \
-e '/password/s/CDATA\[password\]/CDATA\[somethingelse\]/' \
-e '/dbname/s/CDATA\[name\]/CDATA\[somethingdifferent\]/' file.txt

/username/ก่อนที่จะsบอก sed การทำงานเฉพาะในบรรทัดที่มีสตริง 'ชื่อผู้ใช้'


1
สง่างามมีประสิทธิภาพและติดตั้งได้อย่างสมบูรณ์แบบสำหรับเคส +1
ติดตาม

6

หากsedไม่ใช่ข้อกำหนดที่ยากควรใช้เครื่องมือเฉพาะแทน

หากไฟล์ของคุณเป็น XML ที่ถูกต้อง (ไม่ใช่แค่แท็กที่มองหา 3 XML) คุณสามารถใช้XMLStarlet :

xml ed -P -O -L \
  -u '//username/text()' -v 'something' \
  -u '//password/text()' -v 'somethingelse' \
  -u '//dbname/text()' -v 'somethingdifferent' file.xml

ด้านบนจะทำงานในสถานการณ์ที่ยากต่อการแก้ไขด้วยนิพจน์ทั่วไป:

  • สามารถแทนที่ค่าของแท็กโดยไม่ต้องระบุค่าปัจจุบัน
  • สามารถแทนที่ค่าแม้ว่าพวกเขาจะเพิ่งหลบหนีและไม่ได้อยู่ใน CDATA
  • สามารถแทนที่ค่าแม้ว่าแท็กจะมีแอตทริบิวต์
  • สามารถแทนที่แท็กที่เพิ่งเกิดขึ้นได้อย่างง่ายดายหากมีหลายรายการที่มีชื่อเดียวกัน
  • สามารถจัดรูปแบบ XML ที่แก้ไขโดยเยื้องมัน

การสาธิตสั้น ๆ ข้างต้น:

bash-4.2$ cat file.xml
<sith>
<master>
<username><![CDATA[name]]></username>
</master>
<apprentice>
<username><![CDATA[name]]></username>
<password>password</password>
<dbname foo="bar"><![CDATA[name]]></dbname>
</apprentice>
</sith>

bash-4.2$ xml ed -O -u '//apprentice/username/text()' -v 'something' -u '//password/text()' -v 'somethingelse' -u '//dbname/text()' -v 'somethingdifferent' file.xml
<sith>
  <master>
    <username><![CDATA[name]]></username>
  </master>
  <apprentice>
    <username><![CDATA[something]]></username>
    <password>somethingelse</password>
    <dbname foo="bar"><![CDATA[somethingdifferent]]></dbname>
  </apprentice>
</sith>

3

คุณต้องพูด\[.*^$/ในส่วนการแสดงออกปกติของsคำสั่งและ\&/ในส่วนทดแทนรวมถึงการขึ้นบรรทัดใหม่ นิพจน์ทั่วไปเป็นนิพจน์ทั่วไปขั้นพื้นฐานและนอกจากนี้คุณจำเป็นต้องอ้างอิงตัวคั่นสำหรับsคำสั่ง

/คุณสามารถเลือกตัวคั่นที่แตกต่างกันเพื่อหลีกเลี่ยงการพูด คุณจะต้องอ้างอิงตัวละครนั้นแทน แต่โดยปกติแล้วจุดเปลี่ยนของตัวคั่นคือการเลือกตัวที่ไม่ได้เกิดขึ้นในข้อความที่จะแทนที่หรือข้อความแทนที่

sed -e 's~<username><!\[CDATA\[name\]\]></username>~<username><![CDATA[something]]></username>~'

คุณสามารถใช้กลุ่มเพื่อหลีกเลี่ยงการทำซ้ำบางส่วนในข้อความแทนที่และรองรับความหลากหลายของชิ้นส่วนเหล่านี้

sed -e 's~\(<username><!\[[A-Z]*\[\)name\(\]\]></username>\)~\1something\2~'

sed -e 's~\(<username>.*[^A-Za-z]\[\)name\([^A-Za-z].*</username>\)~\1something\2~'

3
$ sed -e '1s/name/something/2' \
      -e '3s/name/somethingdifferent/2' \
      -e 's/password/somethingelse/2' sample.xml

คุณสามารถใช้ที่อยู่ได้ตามหมายเลขที่อยู่ข้างหน้า "s" ซึ่งระบุหมายเลขบรรทัด

นอกจากนี้หมายเลขในท้ายที่สุดก็บอกsedให้แทนที่การแข่งขันนัดที่สองแทนการเปลี่ยนนัดแรก


1

สำหรับการแทนที่คำว่า "name" ด้วยคำว่า "some" ให้ใช้:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml

นั่นจะเป็นการแทนที่การเกิดขึ้นทั้งหมดของคำที่ระบุ

จนถึงทั้งหมดถูกเอาต์พุตไปยังเอาต์พุตมาตรฐานคุณสามารถใช้:

sed "s/\(<username><\!\[[A-Z]*\[\)name\]/\1something/g" file.xml > anotherfile.xml

เพื่อบันทึกการเปลี่ยนแปลงไปยังไฟล์อื่น


0
Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...

    -r, --regexp-extended
             use extended regular expressions in the script.

ดังนั้นเพื่อแทนที่ค่าในไฟล์คุณสมบัติ

sed -i -r 's/MAIL\=(.+)/MAIL\=user@mymail.com/' etc/service.properties 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.