มีบางอย่างเช่น "แยก ()" ของ JavaScript ในเปลือกหรือไม่


18

มันใช้งานง่ายมากsplit()ใน JavaScript เพื่อแยกสตริงออกเป็นอาร์เรย์

เชลล์สคริปต์เป็นอย่างไร

พูดว่าฉันต้องการทำสิ่งนี้:

$ script.sh var1_var2_var3

เมื่อผู้ใช้ให้สตริงดังกล่าวvar1_var2_var3กับ script.sh ภายในสคริปต์มันจะแปลงสตริงเป็นอาร์เรย์เช่น

array=( var1 var2 var3 )
for name in ${array[@]}; do
    # some code
done

1
สิ่งshellที่คุณใช้กับbashที่คุณสามารถทำได้IFS='_' read -a array <<< "${string}"
gwillie

perlก็สามารถทำได้เช่นกัน มันไม่ใช่เปลือกที่ "บริสุทธิ์" แต่มันค่อนข้างธรรมดา
Sobrique

@Sririque ฉันยังไม่ทราบถึงนิยามทางเทคนิคของเชลล์ "pure" แต่มี node.js
emory

ฉันมักจะทำงานใน 'มันอาจจะติดตั้งบนกล่องลินุกซ์ของฉันโดยเริ่มต้นและไม่หงุดหงิดข้อปลีกย่อย :)
Sobrique

คำตอบ:


24

เชลล์คล้าย Bourne / POSIX มีตัวดำเนินการแยก + glob และมีการเรียกใช้ทุกครั้งที่คุณออกจากการขยายพารามิเตอร์ ( $var, $-... ), การแทนที่คำสั่ง ( $(...)) หรือการขยายเลขคณิต ( $((...))) ไม่ได้ระบุไว้ในบริบทรายการ

ที่จริงแล้วคุณเรียกมันโดยไม่ได้ตั้งใจเมื่อคุณทำแทนfor name in ${array[@]} for name in "${array[@]}"(ที่จริงแล้วคุณควรระวังที่เรียกใช้ตัวดำเนินการนั้นโดยไม่ตั้งใจว่าเป็นแหล่งที่มาของข้อบกพร่องและจุดอ่อนด้านความปลอดภัย )

ผู้ประกอบการที่มีการกำหนดค่าด้วย$IFSพารามิเตอร์พิเศษ (จะบอกสิ่งที่ตัวอักษรเพื่อแยก ( แต่ระวังว่าพื้นที่แท็บและการขึ้นบรรทัดใหม่ได้รับการดูแลเป็นพิเศษมี)) และ-fตัวเลือกที่จะปิดการใช้งาน ( set -f) หรือเปิดใช้งาน ( set +f) ด้วยglobส่วนหนึ่ง

นอกจากนี้โปรดทราบว่าในขณะที่Sอิน$IFSคือเดิม (ในเชลล์เป้าหมายที่$IFSมาจาก) สำหรับSeparator ใน POSIX เชลล์อักขระใน$IFSควรจะถูกมองว่าเป็นตัวคั่นหรือเทอร์มิเนเตอร์ (ดูตัวอย่างด้านล่าง)

ดังนั้นเพื่อแยก_:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
array=($string) # invoke the split+glob operator

for i in "${array[@]}"; do # loop over the array elements.

หากต้องการดูความแตกต่างระหว่างตัวคั่นและตัวคั่นให้ลองทำดังนี้:

string='var1_var2_'

ที่จะแยกไว้ในvar1และvar2เท่านั้น (องค์ประกอบที่ว่างเปล่าไม่มีเสริม)

ดังนั้นเพื่อให้คล้ายกับ JavaScript split()คุณต้องมีขั้นตอนเพิ่มเติม:

string='var1_var2_var3'
IFS=_ # delimit on _
set -f # disable the glob part
temp=${string}_ # add an extra delimiter
array=($temp) # invoke the split+glob operator

(โปรดทราบว่ามันจะแบ่งค่าว่าง$stringเป็น1 (ไม่ใช่0 ) องค์ประกอบเช่น JavaScript split())

หากต้องการดูแท็บการรักษาพิเศษพื้นที่และการขึ้นบรรทัดใหม่ให้เปรียบเทียบ:

IFS=' '; string=' var1  var2  '

(ที่คุณได้รับvar1และvar2) ด้วย

IFS='_'; string='_var1__var2__'

ที่คุณจะได้รับ: '', var1, '', ,var2''

โปรดทราบว่าzshเชลล์ไม่ได้เรียกใช้ตัวดำเนินการแยก + glob โดยปริยายเช่นนั้นยกเว้นในshหรือkshจำลอง ที่นั่นคุณต้องเรียกใช้มันอย่างชัดเจน $=stringสำหรับส่วนที่แยก$~stringสำหรับส่วน glob ( $=~stringสำหรับทั้งสอง) และมันยังมีตัวดำเนินการแยกที่คุณสามารถระบุตัวแยก:

array=(${(s:_:)string})

หรือเพื่อรักษาองค์ประกอบที่ว่างเปล่า:

array=("${(@s:_:)string}")

โปรดทราบว่ามีsการแยกไม่คั่น (รวม$IFSถึง POSIX ที่ไม่สอดคล้องกันzsh) มันแตกต่างจาก JavaScript split()ในที่สตริงว่างถูกแบ่งออกเป็นองค์ประกอบ 0 (ไม่ใช่ 1)

ความแตกต่างที่โดดเด่นด้วย$IFS-splitting คือ${(s:abc:)string}แยกบนabcสตริงในขณะที่มีการIFS=abcที่จะแยกa, หรือbc

ด้วยzshและการรักษาพิเศษที่พื้นที่แท็บหรือขึ้นบรรทัดใหม่ได้รับสามารถลบออกได้โดยการเพิ่มพวกเขาในksh93$IFS

ในฐานะที่เป็นบันทึกประวัติศาสตร์เชลล์บอร์น (เชลล์บรรพบุรุษหรือ POSIX เชลล์สมัยใหม่) ลอกองค์ประกอบที่ว่างเปล่าเสมอ นอกจากนี้ยังมีจำนวนของข้อบกพร่องที่เกี่ยวข้องกับการแยกและการขยายตัวของ $ @ $IFSมีค่าที่ไม่ใช่การเริ่มต้นของ ยกตัวอย่างเช่นจะไม่เทียบเท่ากับIFS=_; set -f; set -- $@IFS=_; set -f; set -- $1 $2 $3...

แยกบน regexps

ตอนนี้สำหรับบางสิ่งที่ใกล้เคียงกับ JavaScript split()ที่สามารถแยกการแสดงผลปกติคุณต้องพึ่งพาโปรแกรมอรรถประโยชน์ภายนอก

ใน POSIX Tool-chest awkมีsplitโอเปอเรเตอร์ที่สามารถแยกนิพจน์ปกติแบบขยาย (ซึ่งเป็นชุดย่อยของนิพจน์ปกติเหมือน Perl ที่สนับสนุนโดย JavaScript)

split() {
  awk -v q="'" '
    function quote(s) {
      gsub(q, q "\\" q q, s)
      return q s q
    }
    BEGIN {
      n = split(ARGV[1], a, ARGV[2])
      for (i = 1; i <= n; i++) printf " %s", quote(a[i])
      exit
    }' "$@"
}
string=a__b_+c
eval "array=($(split "$string" '[_+]+'))"

zshเปลือกมีในตัวสนับสนุนสำหรับการแสดงออกปกติ Perl เข้ากันได้ (ในของzsh/pcreโมดูล) แต่ใช้มันเพื่อแยกสตริง แต่ที่เป็นไปได้ค่อนข้างยุ่งยาก


มีเหตุผลสำหรับการรักษาพิเศษที่มีแท็บพื้นที่และขึ้นบรรทัดใหม่หรือไม่?
cuonglm

1
@cuonglm โดยทั่วไปคุณต้องการที่จะแยกกับคำพูดเมื่อตัวคั่นที่มีช่องว่างในกรณีของตัวคั่นที่ไม่ว่าง (เช่นการแยก$PATHบน:) ในทางตรงกันข้ามคุณมักต้องการรักษาองค์ประกอบที่ว่างเปล่า โปรดทราบว่าในเชลล์เป้าหมายตัวละครทุกตัวได้รับการดูแลเป็นพิเศษkshเปลี่ยนให้มีเพียงช่องว่าง (เฉพาะพื้นที่แท็บและการขึ้นบรรทัดใหม่) ที่ได้รับการดูแลเป็นพิเศษ
Stéphane Chazelas

บันทึกย่อ Bourne shell ที่เพิ่มล่าสุดทำให้ฉันประหลาดใจ คุณควรเพิ่มบันทึกย่อสำหรับzshการรักษาด้วยสตริงที่มีอักขระอย่างน้อย 2 ตัว${(s:string:)var}ใช่หรือไม่ หากมีการเพิ่มฉันสามารถลบคำตอบของฉัน :)
cuonglm

1
คุณหมายถึงอะไรโดย "โปรดทราบว่า S ใน $ IFS สำหรับ Delimiter ไม่ใช่ตัวคั่น" ฉันเข้าใจกลไกและที่จะละเว้นต่อท้ายแยก แต่Sยืนแยกไม่คั่น อย่างน้อยนั่นคือสิ่งที่คู่มือทุบตีของฉันพูด
terdon

@terdon $IFSมาจากเชลล์เป้าหมายซึ่งเป็นตัวแยก ksh เปลี่ยนพฤติกรรมโดยไม่เปลี่ยนชื่อ ฉันพูดถึงว่าจะเน้นว่าsplit+glob(ยกเว้นใน zsh หรือ pdksh) ไม่ได้แยกเพียงแค่อีกต่อไป
Stéphane Chazelas

7

ใช่ใช้ IFS_และตั้งค่าให้ จากนั้นใช้read -aเพื่อเก็บลงในอาร์เรย์ ( -rปิดแบ็กสแลชส่วนขยาย) โปรดทราบว่านี่เป็นลักษณะเฉพาะสำหรับการทุบตี; ksh และ zsh มีคุณสมบัติที่คล้ายกันกับไวยากรณ์ที่แตกต่างกันเล็กน้อยและ sh ล้วนไม่มีตัวแปรอาร์เรย์เลย

$ r="var1_var2_var3"
$ IFS='_' read -r -a array <<< "$r"
$ for name in "${array[@]}"; do echo "+ $name"; done
+ var1
+ var2
+ var3

จากman bash:

อ่าน

-a aname

คำที่ถูกกำหนดให้กับดัชนีลำดับของชื่อตัวแปรอาเรย์เริ่มต้นที่ 0 aname จะไม่ถูกตั้งค่าก่อนที่จะกำหนดค่าใหม่ใด ๆ อาร์กิวเมนต์ชื่ออื่นจะถูกละเว้น

ไอเอฟเอ

ตัวแบ่งฟิลด์ภายในที่ใช้สำหรับการแยกคำหลังจากการขยายและเพื่อแยกบรรทัดเป็นคำด้วยคำสั่ง read builtin ค่าเริ่มต้นคือ `` ''

โปรดทราบว่าreadหยุดที่บรรทัดใหม่ครั้งแรก ส่งผ่าน-d ''เพื่อreadหลีกเลี่ยงปัญหานั้น แต่ในกรณีนั้นจะมีการขึ้นบรรทัดใหม่พิเศษในตอนท้ายเนื่องจาก<<<ผู้ดำเนินการ คุณสามารถลบมันได้ด้วยตนเอง:

IFS='_' read -r -d '' -a array <<< "$r"
array[$((${#array[@]}-1))]=${array[$((${#array[@]}-1))]%?}

สมมติว่า$rไม่มีอักขระขึ้นบรรทัดใหม่หรือแบ็กสแลช โปรดทราบด้วยว่าจะใช้งานได้กับbashเชลล์รุ่นล่าสุดเท่านั้น
Stéphane Chazelas

@ StéphaneChazelasเป็นจุดที่ดี ใช่นี่เป็นกรณี "พื้นฐาน" ของสตริง สำหรับส่วนที่เหลือทุกคนควรไปเพื่อคำตอบที่ครอบคลุมของคุณ เกี่ยวกับรุ่นของbash, read -aเป็นที่รู้จักในทุบตี 4 ใช่มั้ย?
fedorqui

1
ขอโทษฉันไม่ดีฉันคิดว่า<<<เพิ่งถูกเพิ่มเข้ามาเมื่อไม่นานมานี้bashแต่ดูเหมือนว่าจะมีมาตั้งแต่ 2.05b (2002) read -aยิ่งแก่กว่านั้น <<<มาจากzshและได้รับการสนับสนุนโดยksh93(และ mksh และ yash) เช่นกัน แต่read -aเป็น bash-specific (เป็น-Aksh93, yash และ zsh)
Stéphane Chazelas

@ StéphaneChazelasมีวิธี "ง่าย" ในการค้นหาเมื่อการเปลี่ยนแปลงเหล่านี้เกิดขึ้น? ฉันพูดว่า "ง่าย" ที่จะไม่ขุดลงในไฟล์ที่วางจำหน่ายอาจเป็นหน้าที่แสดงให้พวกเขาทั้งหมด
fedorqui

1
ฉันดูบันทึกการเปลี่ยนแปลงสำหรับสิ่งนั้น zsh ยังมีที่เก็บ git ที่มีประวัติย้อนหลังไปถึง 3.1.5 และรายชื่อผู้รับจดหมายนั้นใช้สำหรับการติดตามการเปลี่ยนแปลงเช่นกัน
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.