แทนที่หนึ่งซับสตริงสำหรับสตริงอื่นในเชลล์สคริปต์


822

ฉันมี "ฉันรัก Suzi และ Marry" และฉันต้องการเปลี่ยน "Suzi" เป็น "Sara"

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
# do something...

ผลลัพธ์จะต้องเป็นดังนี้:

firstString="I love Sara and Marry"

16
ฉันเริ่มโดยปล่อยให้ Suzi ลงเบา ๆ :)
codesniffer

ไม่ใช่คุณฉันเป็น! นั่นน่าจะเป็นสายที่จะคุยกับ Suzi
GoodSp33d

คำตอบ:


1358

ในการแทนที่การเกิดขึ้นครั้งแรกของรูปแบบด้วยสตริงที่กำหนดให้ใช้:${parameter/pattern/string}

#!/bin/bash
firstString="I love Suzi and Marry"
secondString="Sara"
echo "${firstString/Suzi/$secondString}"    
# prints 'I love Sara and Marry'

หากต้องการแทนที่เหตุการณ์ทั้งหมดให้ใช้:${parameter//pattern/string}

message='The secret code is 12345'
echo "${message//[0-9]/X}"           
# prints 'The secret code is XXXXX'

(นี่คือการบันทึกไว้ในคู่มือทุบตีอ้างอิง , §3.5.3 "ขยายเชลล์พารามิเตอร์" .)

โปรดทราบว่า POSIX ไม่ได้ระบุคุณสมบัตินี้ - เป็นส่วนขยาย Bash - ดังนั้นเชลล์ Unix ทั้งหมดจึงไม่สามารถใช้งานได้ สำหรับเอกสารที่เกี่ยวข้อง POSIX ดูเปิด Group ฐานมาตรฐานข้อมูลจำเพาะทางเทคนิค, ฉบับที่ 7ที่เชลล์และสาธารณูปโภคปริมาณ§2.6.2 "การขยายตัวพารามิเตอร์"


3
@ruakh ฉันจะเขียนคำสั่งนี้ด้วยเงื่อนไขหรืออย่างไร เพียงแค่ฉันต้องการแทนที่ Suzi หรือ Marry ด้วยสตริงใหม่
Priyatham51

3
@ Priyatham51: ไม่มีคุณสมบัติในตัวสำหรับสิ่งนั้น เพียงแค่แทนที่หนึ่งแล้วอื่น ๆ
ruakh

4
@Bu: ไม่เพราะ\nในบริบทนั้นจะเป็นตัวแทนของตัวเองไม่ใช่ขึ้นบรรทัดใหม่ ตอนนี้ฉันไม่มี Bash ที่สะดวกในการทดสอบ แต่คุณควรจะเขียนอะไรบางอย่างเช่น$STRING="${STRING/$'\n'/<br />}". (แม้ว่าคุณอาจต้องการSTRING//- แทนที่ทั้งหมด - แทนที่จะเป็นเพียงSTRING/)
ruakh

25
เพื่อความชัดเจนตั้งแต่นี้ฉันสับสนเล็กน้อยส่วนแรกจะต้องมีการอ้างอิงตัวแปร echo ${my string foo/foo/bar}คุณไม่สามารถทำ คุณต้องการinput="my string foo"; echo ${input/foo/bar}
Henrik N

2
@ mgutt: คำตอบนี้เชื่อมโยงไปยังเอกสารที่เกี่ยวข้อง เอกสารไม่สมบูรณ์ - ตัวอย่างเช่นแทนที่จะพูดถึง//โดยตรงมันกล่าวถึงสิ่งที่เกิดขึ้น "ถ้ารูปแบบเริ่มต้นด้วย ' /'" (ซึ่งไม่แม่นยำแม้แต่เนื่องจากส่วนที่เหลือของประโยคนั้นถือว่าเป็นพิเศษ/ไม่ใช่ส่วนหนึ่งของ รูปแบบ) - แต่ฉันไม่รู้แหล่งที่ดีกว่านี้
ruakh

208

สามารถทำได้ทั้งหมดด้วยการจัดการสตริงทุบตี:

first="I love Suzy and Mary"
second="Sara"
first=${first/Suzy/$second}

สิ่งนั้นจะเข้ามาแทนที่เหตุการณ์แรกเท่านั้น หากต้องการแทนที่ทั้งหมดให้เพิ่มเครื่องหมายทับแรกเป็นสองเท่า:

first="Suzy, Suzy, Suzy"
second="Sara"
first=${first//Suzy/$second}
# first is now "Sara, Sara, Sara"

9
อะไรคือจุดที่มีคำตอบที่ซ้ำกับคำตอบที่ยอมรับได้แทนที่จะแก้ไขคำตอบที่ยอมรับด้วยตัวเลือกการแทนที่ทั่วโลก และเพิ่มที่ regexps ได้รับการสนับสนุนในขณะที่มัน และอธิบายว่าเกิดอะไรขึ้นกับ "การทดแทนที่ไม่ดี" คุณมีคะแนนมากพอ
@Kevin

6
ดูเหมือนว่าพวกเขาทั้งสองตอบในนาทีเดียวกัน: O
Jamie Hutber

76

สำหรับ Dash โพสต์ก่อนหน้าทั้งหมดไม่ทำงาน

shโซลูชันที่รองรับPOSIX คือ:

result=$(echo "$firstString" | sed "s/Suzi/$secondString/")

1
ฉันได้รับสิ่งนี้: $ echo $ (echo $ firstString | sed 's / Suzi / $ secondString / g') ฉันรัก $ secondString และ Marry
Qstnr_La

2
@Qstnr_La ใช้เครื่องหมายคำพูดคู่สำหรับการแทนที่ตัวแปร:result=$(echo $firstString | sed "s/Suzi/$secondString/g")
emc

1
บวก 1 สำหรับการแสดงวิธีการส่งออกไปยังตัวแปรเช่นกัน ขอบคุณ!
เชฟฟาโรห์

2
ฉันแก้ไขเครื่องหมายคำพูดเดี่ยวและเพิ่มเครื่องหมายคำพูดที่หายไปรอบ ๆechoอาร์กิวเมนต์ มันสามารถใช้งานได้โดยไม่ต้องพูดถึงกับสตริงที่เรียบง่าย แต่สามารถแยกสตริงอินพุตที่ไม่ใช่เรื่องง่าย (เว้นวรรคผิดปกติเชลล์เมตาอักขระ)
tripleee

ใน sh (AWS Codebuild / Ubuntu sh) ฉันพบว่าฉันต้องการสแลชหนึ่งครั้งในตอนท้ายไม่ใช่สองครั้ง ฉันจะแก้ไขความคิดเห็นเนื่องจากความคิดเห็นด้านบนยังแสดงเครื่องหมายทับเดียว
ทิม

67

ลองนี้:

 sed "s/Suzi/$secondString/g" <<<"$firstString"

19
คุณไม่จำเป็นต้องมี Sed สำหรับสิ่งนี้ Bash รองรับการเปลี่ยนประเภทนี้โดยกำเนิด
ruakh

12
ฉันเดาว่านี่คือการติดแท็ก "ทุบตี" แต่มาที่นี่เพราะต้องการสิ่งที่ง่ายสำหรับเชลล์อีกอัน นี่เป็นทางเลือกสั้น ๆ ที่ดีสำหรับสิ่งที่wiki.ubuntu.com/…ทำให้มันดูเหมือนว่าฉันต้องการ
natevw

3
วิธีนี้ใช้งานได้ดีสำหรับเถ้า / ประหรือ POSIX sh อื่น ๆ
Yoshua Wuyts

2
ฉันได้รับข้อความแสดงข้อผิดพลาด: -e นิพจน์ # 1, อักขระ 9: ตัวเลือกที่ไม่รู้จักเป็น `s
Nam G VU

1
คำตอบแรกที่ใช้งานได้กับ stdin เหมาะสำหรับการวางสาย
Dinei

46

จะดีกว่าsedถ้าใช้ bash มากกว่าถ้าสตริงมีอักขระ RegExp

echo ${first_string/Suzi/$second_string}

มันเป็นแบบพกพาไปยัง Windows และใช้งานได้กับ Bash 3.1 เป็นอย่างน้อย

เพื่อแสดงว่าคุณไม่ต้องกังวลกับการหลบหนีลองทำสิ่งนี้:

/home/name/foo/bar

เป็นนี้

~/foo/bar

แต่ถ้า/home/nameเป็นเพียงในการเริ่มต้น เราไม่ต้องการsed!

เนื่องจาก bash นั้นทำให้เรามีตัวแปรเวทย์มนตร์$PWDและ$HOMEเราสามารถ:

echo "${PWD/#$HOME/\~}"

แก้ไข:ขอบคุณสำหรับ Mark Haferkamp ในความคิดเห็นเพื่อทราบเกี่ยวกับการอ้างอิง / หลบหนี~*

สังเกตว่าตัวแปร$HOMEมีเครื่องหมายสแลช แต่สิ่งนี้ไม่ทำลายอะไร

อ่านเพิ่มเติม: ขั้นสูงทุบตี Scripting คู่มือ
ถ้าใช้sedเป็นต้องให้แน่ใจว่าได้หลบหนีตัวละครทุกตัว


คำตอบนี้หยุดฉันจากการใช้sedกับpwdคำสั่งเพื่อหลีกเลี่ยงการกำหนดตัวแปรใหม่ทุกครั้งที่กำหนดเองของฉัน$PS1ทำงาน Bash มีวิธีทั่วไปมากกว่าตัวแปรเวทเพื่อใช้เอาต์พุตของคำสั่งสำหรับการแทนที่สตริงหรือไม่? สำหรับโค้ดของคุณฉันต้องหลบเลี่ยง~เพื่อป้องกัน Bash ไม่ให้ขยายเป็น $ HOME นอกจากนี้สิ่งที่#อยู่ในคำสั่งของคุณจะทำอย่างไร
Mark Haferkamp

1
@MarkHaferkamp ดูสิ่งนี้ได้จากลิงก์ "แนะนำให้อ่านเพิ่มเติม" เกี่ยวกับ "การหลบหนี~": สังเกตว่าฉันอ้างถึงสิ่งต่างๆอย่างไร จำไว้ว่าให้พูดทุกสิ่ง! และสิ่งนี้ไม่เพียงทำงานให้กับตัวแปรเวทย์มนตร์เท่านั้น: ตัวแปรใด ๆ ก็ตามที่สามารถทดแทนได้รับความยาวสตริงและอื่น ๆ อีกมากภายในการทุบตี ขอแสดงความยินดีที่คุณพยายาม$PS1อย่างรวดเร็ว: คุณอาจสนใจ$PROMPT_COMMANDถ้าคุณคุ้นเคยกับภาษาการเขียนโปรแกรมอื่นและต้องการรหัสที่คอมไพล์แล้ว
Camilo Martin

ลิงก์ "อ่านเพิ่มเติม" อธิบาย "#" เมื่อวันที่ทุบตี 4.3.30, echo "${PWD/#$HOME/~}"ไม่ได้แทนฉันด้วย$HOME ~แทนที่~ด้วย\~หรือ'~'ทำงาน งานใด ๆ เหล่านี้ใน Bash 4.2.53 ใน distro อื่น คุณช่วยอัพเดทโพสต์ของคุณเพื่ออ้างหรือหลีกเลี่ยง~เพื่อความเข้ากันได้ที่ดีขึ้นหรือไม่? สิ่งที่ฉันหมายถึงโดยคำถาม "ตัวแปรวิเศษ" ของฉันคือ: ฉันสามารถใช้การแทนที่ตัวแปรของ Bash เช่นผลลัพธ์ของunameโดยไม่บันทึกเป็นตัวแปรเป็นอันดับแรกได้หรือไม่ ในฐานะที่เป็นส่วนบุคคลของฉัน$PROMPT_COMMAND, มันซับซ้อน
Mark Haferkamp

@ MarkHaferkamp โอ้โหคุณพูดถูกแล้วไม่ดี จะอัปเดตคำตอบทันที
Camilo Martin

1
@MarkHaferkamp Bash และหลุมพรางชัดเจน ... : P
Camilo Martin

19

ถ้าพรุ่งนี้คุณตัดสินใจว่าคุณไม่รักมารีรี่ย์เธอก็สามารถถูกแทนที่ด้วยเช่นกัน:

today=$(</tmp/lovers.txt)
tomorrow="${today//Suzi/Sara}"
echo "${tomorrow//Marry/Jesica}" > /tmp/lovers.txt

จะต้องมี 50 วิธีในการละทิ้งคู่รักของคุณ


9

เนื่องจากฉันไม่สามารถเพิ่มความคิดเห็น @ruaka เพื่อให้ตัวอย่างอ่านง่ายขึ้นเขียนเช่นนี้

full_string="I love Suzy and Mary"
search_string="Suzy"
replace_string="Sara"
my_string=${full_string/$search_string/$replace_string}
or
my_string=${full_string/Suzy/Sarah}

จนกระทั่งฉันมาถึงตัวอย่างของคุณฉันเข้าใจคำสั่งในทางกลับกัน สิ่งนี้ช่วยให้ชัดเจนว่าเกิดอะไรขึ้น
raghav710

5

เพียวPOSIXวิธีเปลือกซึ่งแตกต่างจากโรมัน Kazanovskyi 's sedคำตอบชั่นไม่จำเป็นต้องเครื่องมือภายนอกเพียงเปลือกของตัวเองพื้นเมืองขยายพารามิเตอร์ โปรดทราบว่าชื่อไฟล์แบบยาวจะย่อเล็กสุดเพื่อให้โค้ดพอดีกับหนึ่งบรรทัด:

f="I love Suzi and Marry"
s=Sara
t=Suzi
[ "${f%$t*}" != "$f" ] && f="${f%$t*}$s${f#*$t}"
echo "$f"

เอาท์พุท:

I love Sara and Marry

มันทำงานอย่างไร:

  • ลบรูปแบบคำต่อท้ายที่เล็กที่สุด "${f%$t*}"ส่งคืน " I love" ถ้าส่วนต่อท้าย$t" Suzi*" อยู่ใน$f" " I love Suzi and Marry

  • แต่ถ้าถ้าt=Zeldaเช่นนั้น"${f%$t*}"จะไม่ลบอะไรเลยและส่งคืนสตริงทั้งหมด " I love Suzi and Marry"

  • ใช้เพื่อทดสอบว่า$tมีการ$fใช้งาน[ "${f%$t*}" != "$f" ]ซึ่งจะประเมินเป็นจริงหาก$fสตริงมี " Suzi*" และเท็จถ้าไม่

  • หากการทดสอบส่งคืนค่าจริงสร้างสตริงที่ต้องการโดยใช้Remove Smallest Suffix Pattern ${f%$t*} " I love" และRemove Smallest Prefix Pattern ${f#*$t} " and Marry" โดยมีสตริงที่2$s " Sara" อยู่ระหว่างนั้น


5

ฉันรู้ว่ามันเก่า แต่เนื่องจากไม่มีใครพูดถึงการใช้ awk:

    firstString="I love Suzi and Marry"
    echo $firstString | awk '{gsub("Suzi","Sara"); print}'

1
เคล็ดลับนั่นคือวิธีที่จะไปหากสิ่งที่คุณพยายามแยกไม่ได้อยู่ในตัวแปร แต่ในไฟล์ข้อความ ขอบคุณ @Payam!
Felipe SS Schneider

2
echo [string] | sed "s/[original]/[target]/g"
  • "s" หมายถึง "ทดแทน"
  • "g" หมายถึง "ทั่วโลกเกิดขึ้นที่ตรงกันทั้งหมด"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.