คำตอบที่เข้ากันได้
มีหลายวิธีในการทำเช่นนี้ค่ะ ทุบตี.
อย่างไรก็ตามสิ่งสำคัญคือต้องทราบก่อนว่าbash
มีคุณสมบัติพิเศษมากมาย (ที่เรียกว่าbashisms ) ที่ไม่สามารถใช้งานได้เปลือก.
โดยเฉพาะอย่างยิ่งอาร์เรย์ , เชื่อมโยงอาร์เรย์และทดแทนรูปแบบซึ่งจะใช้ในการแก้ปัญหาในการโพสต์นี้เช่นเดียวกับคนอื่น ๆ ในหัวข้อที่มีbashismsและอาจไม่สามารถทำงานภายใต้อื่น ๆเปลือกหอยที่หลาย ๆ คนใช้
ตัวอย่างเช่น: บนDebian GNU / Linuxของฉันมีเชลล์มาตรฐานที่เรียกว่าชน; ฉันรู้ว่าหลายคนที่ชอบใช้เปลือกหอยชนิดอื่นที่เรียกว่าksh; และยังมีเครื่องมือพิเศษที่เรียกว่าbusybox ด้วยล่ามเปลือกของเขาเอง (เถ้า)
ขอสตริง
สตริงที่จะแยกในคำถามข้างต้นคือ:
IN="bla@some.com;john@home.com"
ฉันจะใช้เวอร์ชันที่แก้ไขของสตริงนี้เพื่อให้แน่ใจว่าโซลูชันของฉันมีความทนทานต่อสตริงที่มีช่องว่างซึ่งอาจทำลายโซลูชันอื่น ๆ :
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
แยกสตริงตามตัวคั่นใน ทุบตี (เวอร์ชั่น> = 4.2)
โดยบริสุทธิ์ bash
เราสามารถสร้างอาร์เรย์ที่มีองค์ประกอบแยกตามค่าชั่วคราวสำหรับIFS ( ตัวคั่นฟิลด์อินพุต ) IFS เหนือสิ่งอื่นใดจะบอกว่าbash
ตัวละครตัวไหนที่ควรใช้เป็นตัวคั่นระหว่างองค์ประกอบเมื่อกำหนดอาร์เรย์:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS
ในเวอร์ชันที่ใหม่กว่าbash
คำนำหน้าคำสั่งที่มีนิยาม IFS จะเปลี่ยน IFS สำหรับคำสั่งนั้นเท่านั้นและรีเซ็ตเป็นค่าก่อนหน้าทันทีหลังจากนั้น นี่หมายความว่าเราสามารถทำข้างต้นได้ในหนึ่งบรรทัด
IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'
เราจะเห็นว่าสตริงIN
นั้นถูกเก็บไว้ในอาร์เรย์ชื่อfields
แยกบนอัฒภาค:
set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'
(เรายังสามารถแสดงเนื้อหาของตัวแปรเหล่านี้โดยใช้declare -p
:)
declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
โปรดทราบว่าread
เป็นวิธีที่เร็วที่สุดในการแยกเนื่องจากไม่มีส้อมหรือทรัพยากรภายนอกที่เรียกว่า
เมื่อกำหนดอาร์เรย์แล้วคุณสามารถใช้การวนรอบอย่างง่าย ๆ ในการประมวลผลแต่ละฟิลด์ (หรือแทนแต่ละองค์ประกอบในอาร์เรย์ที่คุณกำหนดไว้ตอนนี้)
# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
echo "> [$x]"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
หรือคุณสามารถปล่อยแต่ละฟิลด์จากอาร์เรย์หลังจากประมวลผลโดยใช้วิธีการเลื่อนซึ่งฉันชอบ:
while [ "$fields" ] ;do
echo "> [$fields]"
# slice the array
fields=("${fields[@]:1}")
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
และถ้าคุณต้องการเพียงแค่พิมพ์อาเรย์ง่ายๆคุณไม่จำเป็นต้องวนซ้ำมัน:
printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
อัปเดต: ล่าสุด ทุบตี > = 4.4
ในเวอร์ชันที่ใหม่กว่าของbash
คุณยังสามารถเล่นกับคำสั่งmapfile
:
mapfile -td \; fields < <(printf "%s\0" "$IN")
ไวยากรณ์นี้เก็บรักษาตัวอักษรพิเศษบรรทัดใหม่และฟิลด์ว่าง!
หากคุณไม่ต้องการรวมฟิลด์ว่างคุณสามารถทำสิ่งต่อไปนี้:
mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}") # drop '\n' added by '<<<'
ด้วยmapfile
คุณสามารถข้ามการประกาศอาร์เรย์และ "วน" โดยปริยายบนองค์ประกอบที่คั่นด้วยการเรียกใช้ฟังก์ชันในแต่ละรายการ:
myPubliMail() {
printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
# mail -s "This is not a spam..." "$2" </path/to/body
printf "\e[3D, done.\n"
}
mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail
(หมายเหตุ: \0
สตริงที่จุดสิ้นสุดของรูปแบบไม่มีประโยชน์หากคุณไม่สนใจฟิลด์ว่างที่ท้ายสตริงหรือไม่ปรากฏอยู่)
mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
หรือคุณสามารถใช้<<<
และในส่วนของฟังก์ชั่นนั้นมีการประมวลผลบางอย่างเพื่อวางบรรทัดใหม่ที่มันเพิ่ม:
myPubliMail() {
local seq=$1 dest="${2%$'\n'}"
printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
# mail -s "This is not a spam..." "$dest" </path/to/body
printf "\e[3D, done.\n"
}
mapfile <<<"$IN" -td \; -c 1 -C myPubliMail
# Renders the same output:
# Seq: 0: Sending mail to 'bla@some.com', done.
# Seq: 1: Sending mail to 'john@home.com', done.
# Seq: 2: Sending mail to 'Full Name <fulnam@other.org>', done.
แยกสตริงตามตัวคั่นใน เปลือก
หากคุณไม่สามารถใช้งานได้bash
หรือถ้าคุณต้องการเขียนสิ่งที่สามารถใช้ได้ในหลาย ๆ เชลล์คุณมักจะไม่ใช้bashisms - ซึ่งรวมถึงอาร์เรย์ที่เราใช้ในการแก้ปัญหาข้างต้น
อย่างไรก็ตามเราไม่จำเป็นต้องใช้อาร์เรย์ในการวนซ้ำ "องค์ประกอบ" ของสตริง มีไวยากรณ์ที่ใช้ในเชลล์จำนวนมากสำหรับการลบสตริงย่อยของสตริงจากการเกิดขึ้นครั้งแรกหรือครั้งสุดท้ายของรูปแบบ โปรดทราบว่า*
เป็นอักขระตัวแทนที่หมายถึงอักขระศูนย์หรือมากกว่า:
(การขาดวิธีการนี้ในการแก้ไขปัญหาใด ๆ ที่โพสต์จนถึงเป็นเหตุผลหลักที่ฉันเขียนคำตอบนี้)
${var#*SubStr} # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*} # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string
ตามที่อธิบายโดยScore_Under :
#
และ%
ลบสตริงย่อยที่ตรงกันที่สั้นที่สุดที่เป็นไปได้จากจุดเริ่มต้นและจุดสิ้นสุดของสตริงตามลำดับและ
##
และ%%
ลบสตริงย่อยที่ตรงกันที่ยาวที่สุดที่เป็นไปได้
การใช้ไวยากรณ์ข้างต้นเราสามารถสร้างวิธีการที่เราแยก "องค์ประกอบ" สตริงย่อยจากสตริงโดยการลบสตริงย่อยขึ้นไปหรือหลังตัวคั่น
codeblock ด้านล่างใช้งานได้ดี ทุบตี(รวมถึงของ Mac OS bash
)ชน, kshและ busybox's เถ้า:
IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
# extract the substring from start of string up to delimiter.
# this is the first "element" of the string.
iter=${IN%%;*}
echo "> [$iter]"
# if there's only one element left, set `IN` to an empty string.
# this causes us to exit this `while` loop.
# else, we delete the first "element" of the string from IN, and move onto the next.
[ "$IN" = "$iter" ] && \
IN='' || \
IN="${IN#*;}"
done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]
มีความสุข!