วิธีรวบรวมอาร์เรย์ของเส้นอย่างถูกต้องใน zsh


42

ฉันคิดว่าต่อไปนี้จะจัดกลุ่มผลลัพธ์ของmy_commandอาร์เรย์หลายบรรทัด:

IFS='\n' array_of_lines=$(my_command);

ดังนั้น$array_of_lines[1]จะหมายถึงบรรทัดแรกในผลลัพธ์ของmy_command, $array_of_lines[2]ถึงวินาทีและอื่น ๆ

อย่างไรก็ตามคำสั่งด้านบนนั้นใช้งานไม่ได้ ดูเหมือนว่าจะแยกเอาท์พุทของmy_commandตัวละครรอบตัวnออกไปตามที่ฉันได้ตรวจสอบด้วยprint -l $array_of_linesซึ่งฉันเชื่อว่ามันจะพิมพ์องค์ประกอบของอาเรย์ทีละบรรทัด ฉันได้ตรวจสอบสิ่งนี้ด้วย:

echo $array_of_lines[1]
echo $array_of_lines[2]
...

ในความพยายามครั้งที่สองฉันคิดว่าการเพิ่มevalจะช่วยได้:

IFS='\n' array_of_lines=$(eval my_command);

แต่ฉันได้ผลลัพธ์เหมือนกันทุกประการ

ในที่สุดต่อไปนี้คำตอบเกี่ยวกับองค์ประกอบของรายการที่มีช่องว่างใน zshฉันยังได้ลองใช้การตั้งค่าสถานะการขยายพารามิเตอร์แทนที่จะIFSบอก zsh วิธีการแยกอินพุตและรวบรวมองค์ประกอบเป็นอาร์เรย์เช่น:

array_of_lines=("${(@f)$(my_command)}");

แต่ฉันยังคงได้รับผลลัพธ์เดียวกัน (แยกเกิดขึ้นn)

ด้วยสิ่งนี้ฉันมีคำถามต่อไปนี้:

ไตรมาสที่ 1 อะไรคือ"วิธีการที่เหมาะสม"ในการรวบรวมเอาต์พุตของคำสั่งในอาร์เรย์ของบรรทัด?

ไตรมาสที่ 2 ฉันIFSจะระบุให้แยกบรรทัดใหม่ได้อย่างไร

ไตรมาสที่ 3 หากฉันใช้การตั้งค่าสถานะการขยายพารามิเตอร์เช่นเดียวกับในความพยายามครั้งที่สามของฉันข้างต้น (เช่นการใช้@f) เพื่อระบุการแยก zsh จะละเว้นค่าเป็นIFSหรือไม่? ทำไมมันไม่ทำงานด้านบน

คำตอบ:


71

TL, DR:

array_of_lines=("${(@f)$(my_command)}")

ผิดพลาดครั้งแรก (→ไตรมาสที่ 2): IFS='\n'ชุดIFSกับตัวละครทั้งสองและ\ nในการตั้งค่าการขึ้นบรรทัดใหม่ใช้IFSIFS=$'\n'

ความผิดพลาดที่สอง: array_of_lines=(foo bar)การตั้งค่าตัวแปรให้ค่าอาร์เรย์คุณต้องวงเล็บรอบองค์ประกอบ:

สิ่งนี้จะทำงานได้ยกเว้นว่าจะตัดเส้นที่ว่างเปล่าเนื่องจากช่องว่างต่อเนื่องนับเป็นตัวคั่นเดียว:

IFS=$'\n' array_of_lines=($(my_command))

คุณสามารถเก็บบรรทัดว่างไว้ได้ยกเว้นตอนท้ายสุดโดยเพิ่มอักขระ whitespace เป็นสองเท่าในIFS:

IFS=$'\n\n' array_of_lines=($(my_command))

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

IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'

(สมมติว่าผลลัพธ์ของmy_commandไม่สิ้นสุดในบรรทัดที่ไม่มีการคั่นด้วยโปรดทราบว่าคุณสูญเสียสถานะการออกของmy_command)

โปรดทราบว่าตัวอย่างทั้งหมดข้างต้นIFSมีค่าที่ไม่ใช่ค่าเริ่มต้นดังนั้นพวกเขาอาจทำให้รหัสที่ตามมาสับสน เพื่อคงการตั้งค่าของIFSโลคัลให้ใส่ทุกสิ่งลงในฟังก์ชั่นที่คุณประกาศIFSท้องถิ่น (ที่นี่ยังดูแลการคงสถานะการออกของคำสั่งไว้):

collect_lines() {
  local IFS=$'\n\n' ret
  array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
  ret=$?
  unset 'array_of_lines[-1]'
  return $ret
}
collect_lines my_command

แต่ฉันแนะนำไม่ให้ยุ่งกับIFS; ให้ใช้การfตั้งค่าการขยายเพื่อแยกบรรทัดใหม่ (→ Q1):

array_of_lines=("${(@f)$(my_command)}")

หรือเพื่อรักษาบรรทัดว่างต่อท้าย:

array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'

คุณค่าของIFSไม่สำคัญ ฉันสงสัยว่าคุณใช้คำสั่งที่แยกIFSเพื่อพิมพ์$array_of_linesในการทดสอบของคุณ (→ Q3)


7
มันซับซ้อนมาก! "${(@f)...}"เหมือนกัน${(f)"..."}แต่แตกต่างกัน (@)ภายในเครื่องหมายคำพูดคู่หมายถึง“ ให้ผลตอบแทนหนึ่งคำต่อองค์ประกอบอาร์เรย์” และ(f)หมายถึง“ แบ่งออกเป็นอาร์เรย์โดยขึ้นบรรทัดใหม่” PS: โปรดลิงค์ไปที่เอกสาร
แกะบิน

3
@flyingsheep ${(f)"..."}จะไม่ข้ามบรรทัดว่าง"${(@f)...}"ไว้และเก็บรักษาไว้ นั่นคือความแตกต่างที่เหมือนกันระหว่างและ$argv "$argv[@]"ว่า"$@"สิ่งที่จะต้องรักษาองค์ประกอบทั้งหมดของอาร์เรย์มาจากเปลือกบอร์นในช่วงปลายทศวรรษที่ 70
Stéphane Chazelas

4

ปัญหาสองประการ: ประการแรกคำพูดสองเท่าที่เห็นได้ชัดก็ไม่ได้ตีความเครื่องหมายทับขวา (ขออภัยเกี่ยวกับเรื่องนั้น :) ใช้$'...'คำพูด และตามการman zshparamรวบรวมคำในอาร์เรย์คุณจำเป็นต้องใส่ไว้ในวงเล็บ ดังนั้นงานนี้:

% touch 'a b' c d 'e f'
% IFS=$'\n' arr=($(ls)); print -l $arr
a b
c
d
e f
% print $arr[1]
a b

ฉันตอบคำถาม Q3 ของคุณไม่ได้ ฉันหวังว่าฉันจะไม่ต้องรู้เรื่องลึกลับอีกเลย :)


-2

คุณยังสามารถใช้ tr เพื่อแทนที่บรรทัดใหม่ด้วยช่องว่าง:

lines=($(mycommand | tr '\n' ' '))
select line in ("${lines[@]}"); do
  echo "$line"
  break
done

3
เกิดอะไรขึ้นถ้าบรรทัดมีช่องว่าง?
don_crissti

2
มันไม่สมเหตุสมผลเลย ทั้ง SPC และ NL $IFSอยู่ในค่าเริ่มต้นของ การแปลอย่างใดอย่างหนึ่งไม่ทำให้ความแตกต่าง
Stéphane Chazelas

การแก้ไขของฉันสมเหตุสมผลหรือไม่ ฉันไม่สามารถทำงานได้เหมือนเดิม
จอห์นพี

(หมดเวลา) ฉันยอมรับว่าฉันแก้ไขมันโดยไม่เข้าใจว่าเจตนาคืออะไร แต่ฉันคิดว่าการแปลนั้นเป็นการเริ่มต้นที่ดีสำหรับการแยกโดยใช้การจัดการสตริง ฉันจะไม่แปลเป็นช่องว่างและแยกมันออกนอกเสียจากว่าพฤติกรรมที่คาดหวังจะเป็นเช่นนั้นมากขึ้นechoโดยที่อินพุตมีจำนวนคำมากหรือน้อยแยกจากกันโดยผู้ที่สนใจ
John P
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.