ลบคำนำหน้า / คำต่อท้ายคงที่จากสตริงใน Bash


485

ในbashสคริปต์ของฉันฉันมีสตริงและคำนำหน้า / คำต่อท้าย ฉันต้องการลบคำนำหน้า / คำต่อท้ายจากสตริงเดิม

ตัวอย่างเช่นสมมติว่าฉันมีค่าต่อไปนี้:

string="hello-world"
prefix="hell"
suffix="ld"

ฉันจะรับผลลัพธ์ต่อไปนี้ได้อย่างไร

result="o-wor"


14
ระวังให้มากเมื่อเชื่อมโยงไปยังคำแนะนำการใช้สคริปต์การทุบตีขั้นสูง มันมีส่วนผสมของคำแนะนำที่ดีและน่ากลัว
tripleee

คำตอบ:


719
$ foo=${string#"$prefix"}
$ foo=${foo%"$suffix"}
$ echo "${foo}"
o-wor

40
นอกจากนี้ยังมี ## และ %% ซึ่งจะลบได้มากที่สุดหากคำนำหน้า $ หรือคำต่อท้าย $ มีอักขระตัวแทน
จุด

28
มีวิธีรวมสองในหนึ่งบรรทัดหรือไม่ ฉันพยายาม${${string#prefix}%suffix}แต่มันใช้งานไม่ได้
static_rtti

28
@static_rtti ไม่น่าเสียดายที่คุณไม่สามารถซ้อนการทดแทนพารามิเตอร์เช่นนี้ได้ ฉันรู้ว่ามันเป็นความอัปยศ
Adrian Frühwirth

87
@ AdrianFrühwirth: ภาษาทั้งหมดเป็นความอัปยศ แต่มีประโยชน์มาก :)
static_rtti

8
Nvm "bash sub ทดแทน" ใน Google พบสิ่งที่ฉันต้องการ
ไทเลอร์

89

ใช้ sed:

$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

ภายในคำสั่ง sed ที่^ตัวละครตรงกับจุดเริ่มต้นข้อความด้วย$prefixและต่อท้ายตรงกับข้อความที่ลงท้ายด้วย$$suffix

Adrian Frühwirthให้คะแนนที่ดีในความคิดเห็นด้านล่าง แต่sedสำหรับจุดประสงค์นี้อาจมีประโยชน์มาก ความจริงที่ว่าเนื้อหาของ $ prefix และ $ suffix ถูกตีความโดย sed อาจเป็นได้ทั้งดีหรือไม่ดีตราบใดที่คุณให้ความสนใจคุณก็ควรจะปรับ ความงามคือคุณสามารถทำสิ่งนี้:

$ prefix='^.*ll'
$ suffix='ld$'
$ echo "$string" | sed -e "s/^$prefix//" -e "s/$suffix$//"
o-wor

ซึ่งอาจเป็นสิ่งที่คุณต้องการและมีทั้งนักเล่นและมีประสิทธิภาพมากกว่าการแทนที่ตัวแปร bash หากคุณจำได้ว่ามีพลังอันยิ่งใหญ่มาพร้อมความรับผิดชอบที่ดี (ตามที่ Spiderman พูด) คุณควรจะสบายดี

คำแนะนำเบื้องต้นเกี่ยวกับ sed สามารถดูได้ที่http://evc-cit.info/cit052/sed_tutorial.html

หมายเหตุเกี่ยวกับเชลล์และการใช้สตริง:

สำหรับตัวอย่างที่ระบุไว้ต่อไปนี้จะทำงานได้เช่นกัน:

$ echo $string | sed -e s/^$prefix// -e s/$suffix$//

... แต่เพียงเพราะ:

  1. echo ไม่สนใจว่ามีกี่สายในรายการอาร์กิวเมนต์และ
  2. ไม่มีช่องว่างในคำนำหน้า $ และคำต่อท้าย $

เป็นแนวปฏิบัติที่ดีในการอ้างอิงสตริงในบรรทัดคำสั่งเพราะแม้ว่าจะมีช่องว่างมันจะถูกนำเสนอให้กับคำสั่งเป็นอาร์กิวเมนต์เดียว เราอ้างอิง $ prefix และ $ suffix ด้วยเหตุผลเดียวกัน: แต่ละคำสั่ง edit เพื่อ sed จะถูกส่งเป็นสตริงเดียว เราใช้เครื่องหมายคำพูดคู่เพราะอนุญาตสำหรับการแก้ไขตัวแปร ถ้าเราใช้เครื่องหมายคำพูดเดี่ยวคำสั่ง sed จะได้รับตัวอักษร$prefixและ$suffixซึ่งแน่นอนว่าไม่ใช่สิ่งที่เราต้องการ

แจ้งให้ทราบล่วงหน้าเกินไปใช้งานของฉันราคาเดียวเมื่อตั้งค่าตัวแปรและprefix suffixแน่นอนว่าเราไม่ต้องการอะไรที่จะตีความดังนั้นเราจึงอ้างถึงมันเพื่อไม่ให้เกิดการแก้ไข อีกครั้งอาจไม่จำเป็นในตัวอย่างนี้ แต่เป็นนิสัยที่ดีในการเข้า


8
น่าเสียดายที่นี่เป็นคำแนะนำที่ไม่ดีด้วยเหตุผลหลายประการ: 1) ไม่ได้$stringกล่าวถึง 2) $prefixและ$suffixสามารถมีการแสดงออกที่sedจะตีความเช่นการแสดงออกปกติหรือตัวละครที่ใช้เป็นตัวคั่นที่จะทำลายคำสั่งทั้งหมด 3) sedไม่จำเป็นต้องโทรสองครั้ง (คุณสามารถ-e 's///' -e '///'แทนได้) และสามารถหลีกเลี่ยงท่อได้ ตัวอย่างเช่นพิจารณาstring='./ *'และ / หรือprefix='./'และเห็นมันทำลายอย่างน่ากลัวเนื่องจากการและ1) 2)
Adrian Frühwirth

หมายเหตุสนุก: sed สามารถใช้เกือบทุกอย่างเป็นตัวคั่น ในกรณีของฉันเนื่องจากฉันกำลังแยกคำนำหน้าไดเรกทอรีออกจากเส้นทางฉันไม่สามารถใช้/ดังนั้นฉันใช้sed "s#^$prefix##แทน (ความเปราะบาง: ชื่อไฟล์ไม่สามารถมี#ได้เนื่องจากฉันควบคุมไฟล์เราจึงปลอดภัยที่นั่น)
Olie

@Olie ชื่อไฟล์สามารถมีอักขระใด ๆยกเว้นอักขระเครื่องหมายทับและอักขระ null ดังนั้นหากคุณไม่สามารถควบคุมได้คุณจะไม่สามารถสมมติว่าชื่อไฟล์มีอักขระบางตัวได้
Adrian Frühwirth

ใช่ไม่รู้ว่าฉันคิดอะไรอยู่ที่นั่น iOS อาจจะ? dunno ชื่อไฟล์สามารถมี "#" ได้อย่างแน่นอน ไม่รู้เลยว่าทำไมฉันถึงพูดแบบนั้น :)
Olie

@Olie: เมื่อฉันเข้าใจความคิดเห็นดั้งเดิมของคุณคุณกำลังบอกว่าข้อ จำกัด ที่คุณเลือกใช้#เป็นตัวคั่นของ sed หมายความว่าคุณไม่สามารถจัดการไฟล์ที่มีตัวอักษรนั้นได้
P Daddy

17

คุณรู้ความยาวของคำนำหน้าและคำต่อท้ายของคุณหรือไม่? ในกรณีของคุณ:

result=$(echo $string | cut -c5- | rev | cut -c3- | rev)

หรือมากกว่าทั่วไป:

result=$(echo $string | cut -c$((${#prefix}+1))- | rev | cut -c$((${#suffix}+1))- | rev)

แต่ทางออกจาก Adrian Frühwirthนั้นช่างยอดเยี่ยม! ฉันไม่รู้เกี่ยวกับสิ่งนั้น!


14

ฉันใช้ grep สำหรับการลบคำนำหน้าจากเส้นทาง (ซึ่งไม่ได้รับการจัดการอย่างดีsed):

echo "$input" | grep -oP "^$prefix\K.*"

\K ลบออกจากการจับคู่ตัวละครทั้งหมดก่อนที่จะ


grep -Pเป็นส่วนขยายที่ไม่เป็นมาตรฐาน มีพลังมากขึ้นสำหรับคุณถ้ามันรองรับแพลตฟอร์มของคุณ แต่นี่เป็นคำแนะนำที่น่าสงสัยหากรหัสของคุณต้องพกพาได้อย่างสมเหตุสมผล
tripleee

@tripleee แน่นอน แต่ฉันคิดว่าระบบที่ติดตั้ง GNU Bash ยังมี grep ที่รองรับ PCRE
Vladimir Petrakovich

1
ไม่มี MacOS ตัวอย่างเช่นมีทุบตีออกจากกล่อง แต่ไม่ grepGNU เวอร์ชันก่อนหน้ามี-Pตัวเลือกจาก BSD จริง ๆgrepแต่มีการลบออก
tripleee

9
$ string="hello-world"
$ prefix="hell"
$ suffix="ld"

$ #remove "hell" from "hello-world" if "hell" is found at the beginning.
$ prefix_removed_string=${string/#$prefix}

$ #remove "ld" from "o-world" if "ld" is found at the end.
$ suffix_removed_String=${prefix_removed_string/%$suffix}
$ echo $suffix_removed_String
o-wor

หมายเหตุ:

# $ prefix: การเพิ่ม # ทำให้แน่ใจว่าจะลบสตริงย่อย "hell" เฉพาะเมื่อพบในตอนเริ่มต้นเท่านั้น % $ คำต่อท้าย: การเพิ่ม% ทำให้แน่ใจว่าจะลบสตริงย่อย "ld" เฉพาะเมื่อพบในตอนท้าย

หากไม่มีสิ่งเหล่านี้สตริงย่อย "นรก" และ "ld" จะถูกลบออกไปทุกที่แม้จะอยู่ตรงกลาง


ขอบคุณสำหรับหมายเหตุ! qq: ในตัวอย่างโค้ดของคุณคุณมีเครื่องหมายสแลช/ตรงข้างหลังสตริงสิ่งนั้นมีไว้เพื่ออะไร?
DiegoSalazar

1
/ แยกสตริงปัจจุบันและสตริงย่อย สตริงย่อยที่นี่เป็นคำต่อท้ายในคำถามที่โพสต์
Vijay Vat


6

โซลูชันขนาดเล็กและสากล:

expr "$string" : "$prefix\(.*\)$suffix"

1
หากคุณกำลังใช้ Bash คุณอาจไม่ควรใช้exprเลย มันเป็นความเรียงลำดับของห้องครัวที่สะดวกสาธารณูปโภคอ่างล้างจานกลับมาในวันที่ของเดิม Bourne เปลือก แต่ตอนนี้วิธีที่ดีที่สุดที่ผ่านมาก่อนวันที่
tripleee

5

ใช้คำตอบ @Adrian Frühwirth:

function strip {
    local STRING=${1#$"$2"}
    echo ${STRING%$"$2"}
}

ใช้มันแบบนี้

HELLO=":hello:"
HELLO=$(strip "$HELLO" ":")
echo $HELLO # hello

0

ฉันจะใช้ประโยชน์จากกลุ่มการจับภาพใน regex:

$ string="hello-world"
$ prefix="hell"
$ suffix="ld"
$ set +H # Disables history substitution, can be omitted in scripts.
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/" <<< $string
o-wor
$ string1=$string$string
$ perl -pe "s/${prefix}((?:(?!(${suffix})).)*)${suffix}/\1/g" <<< $string1
o-woro-wor

((?:(?!(${suffix})).)*)ทำให้แน่ใจว่าเนื้อหาของ${suffix}จะถูกแยกออกจากกลุ่มการดักจับ [^A-Z]*ในแง่ของการเช่นนั้นเทียบเท่าสตริง มิฉะนั้นคุณจะได้รับ:

$ perl -pe "s/${prefix}(.*)${suffix}/\1/g" <<< $string1
o-worldhello-wor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.