เมื่อใดที่ฉันสามารถใช้ IFS ชั่วคราวเพื่อแยกฟิลด์ได้


19

ในทุบตีว่าคุณมีvar=a.b.c.แล้ว:

$ IFS=. printf "%s\n" $var
a.b.c

อย่างไรก็ตามการใช้งานดังกล่าวIFSจะมีผลในขณะที่สร้างอาร์เรย์:

$ IFS=. arr=($var)
$ printf "%s\n" "${arr[@]}"
a
b
c

สะดวกมากแน่นอน แต่เอกสารนี้อยู่ที่ไหน การอ่านส่วนต่าง ๆ ของAraysหรือWord Splittingอย่างรวดเร็วในเอกสาร Bash นั้นไม่ได้บ่งชี้อะไรเลย ค้นหาIFSผ่านเอกสารหน้าเดียวไม่ได้ให้คำแนะนำเกี่ยวกับผลกระทบนี้

ฉันไม่แน่ใจว่าเมื่อฉันสามารถทำอย่างน่าเชื่อถือ:

IFS=x do something

และคาดว่าIFSจะมีผลต่อการแยกฟิลด์

คำตอบ:


28

แนวคิดพื้นฐานคือว่าVAR=VALUE some-commandชุดVARการVALUEสำหรับการดำเนินการsome-commandเมื่อsome-commandเป็นคำสั่งภายนอกและจะไม่ได้รับแฟนซียิ่งไปกว่านั้น หากคุณรวมปรีชานี้เข้ากับความรู้ว่าเชลล์ทำงานอย่างไรคุณควรได้คำตอบที่ถูกต้องในกรณีส่วนใหญ่ อ้างอิง POSIX คือSimple“คำสั่ง” ในบท“เชลล์ภาษาคำสั่ง”

ถ้าsome-commandเป็นคำสั่งภายนอก , เทียบเท่ากับVAR=VALUE some-command ถูกส่งออกในสภาพแวดล้อมของและมูลค่าของมัน (หรือขาดค่า) ในเปลือกจะไม่เปลี่ยนแปลงenv VAR=VALUE some-commandVARsome-command

ถ้าsome-commandเป็นฟังก์ชั่นก็VAR=VALUE some-commandจะเทียบเท่ากับVAR=VALUE; some-commandเช่นการมอบหมายยังคงอยู่ในสถานที่หลังจากฟังก์ชั่นได้กลับมาและตัวแปรจะไม่ถูกส่งออกไปยังสภาพแวดล้อม เหตุผลที่เกี่ยวข้องกับการออกแบบของเชลล์เป้าหมาย (และต่อมาเมื่อมีความเข้ากันได้แบบย้อนหลัง): ไม่มีสิ่งอำนวยความสะดวกในการบันทึกและกู้คืนค่าตัวแปรรอบการดำเนินการของฟังก์ชั่น การไม่ส่งออกตัวแปรนั้นสมเหตุสมผลเนื่องจากฟังก์ชั่นทำงานในเชลล์เอง อย่างไรก็ตาม ksh (รวมทั้ง ATT ksh93 และ pdksh / mksh), bash และ zsh ใช้พฤติกรรมที่มีประโยชน์มากขึ้นซึ่งVARถูกตั้งค่าเฉพาะระหว่างการเรียกใช้ฟังก์ชัน (มันถูกส่งออกด้วย) ในkshสิ่งนี้จะเกิดขึ้นหากฟังก์ชันถูกกำหนดด้วยไวยากรณ์ kshfunction NAME …NAME ()ไม่ได้ถ้ามันกำหนดด้วยไวยากรณ์มาตรฐาน ในbashจะทำเฉพาะในโหมด bash ไม่ใช่ในโหมด POSIX (เมื่อทำงานด้วยPOSIXLY_CORRECT=1) ในzshสิ่งนี้จะเกิดขึ้นหากposix_builtinsไม่ได้ตั้งค่าตัวเลือก ตัวเลือกนี้ไม่ได้ถูกกำหนดโดยค่าเริ่มต้น แต่จะเปิดอยู่ตามหรือemulate shemulate ksh

ถ้าsome-commandเป็น builtin พฤติกรรมจะขึ้นอยู่กับประเภทของ builtin builtins พิเศษทำตัวเหมือนฟังก์ชั่น บิวด์อินพิเศษคือสิ่งที่ต้องดำเนินการภายในเชลล์เนื่องจากมีผลกับสถานะเชลล์ (เช่นbreakมีผลต่อโฟลว์ควบคุม, cdส่งผลต่อไดเรกทอรีปัจจุบัน, setส่งผลต่อพารามิเตอร์ตำแหน่งและตัวเลือก ... ) builtins อื่น ๆมีมาให้เพื่อประสิทธิภาพและความสะดวกสบาย (ส่วนใหญ่ - เช่นคุณสมบัติทุบตีprintf -vสามารถใช้งานได้โดย builtin เท่านั้น) และทำงานเหมือนคำสั่งภายนอก

การมอบหมายเกิดขึ้นหลังจากการขยายนามแฝงดังนั้นหากsome-commandเป็นนามแฝงให้ขยายก่อนเพื่อค้นหาว่าเกิดอะไรขึ้น

โปรดทราบว่าในทุกกรณีการมอบหมายจะดำเนินการหลังจากบรรทัดคำสั่งถูกแยกวิเคราะห์รวมถึงการทดแทนตัวแปรใด ๆ ในบรรทัดคำสั่งเอง ดังนั้นจึงvar=a; var=b echo $varพิมพ์aเพราะ$varได้รับการประเมินก่อนที่จะมีการมอบหมาย และทำให้การIFS=. printf "%s\n" $varใช้เก่าคุ้มค่าที่จะแยกIFS$var

ฉันได้ครอบคลุมทุกประเภทของคำสั่ง แต่มีอีกกรณีหนึ่ง: เมื่อไม่มีคำสั่งให้ดำเนินการคือถ้าคำสั่งประกอบด้วยการมอบหมายเท่านั้น (และอาจเป็นการเปลี่ยนเส้นทาง) ในกรณีที่ได้รับมอบหมายยังคงอยู่ในสถานที่ เทียบเท่ากับVAR=VALUE OTHERVAR=OTHERVALUE VAR=VALUE; OTHERVAR=OTHERVALUEดังนั้นหลังจากที่IFS=. arr=($var), ยังคงตั้งค่าให้IFS .เนื่องจากคุณสามารถใช้$IFSในการมอบหมายให้arrกับความคาดหวังที่ว่ามันมีอยู่แล้วค่าใหม่ของมันก็ทำให้รู้สึกว่าค่าใหม่ของใช้สำหรับการขยายตัวของIFS$var

โดยสรุปคุณสามารถใช้IFSสำหรับการแยกฟิลด์ชั่วคราวเท่านั้น:

  • โดยการเริ่มเชลล์ใหม่หรือ subshell (เช่นthird=$(IFS=.; set -f; set -- $var; echo "$3")เป็นวิธีที่ซับซ้อนในการทำthird=${var#*.*.}ยกเว้นว่าพวกเขาทำงานแตกต่างกันเมื่อค่าของvarประกอบด้วยน้อยกว่าสอง.อักขระ);
  • ใน ksh โดยIFS=. some-functionที่some-functionมีการกำหนดด้วยไวยากรณ์ ksh function some-function …;
  • ใน bash และ zsh IFS=. some-functionตราบใดที่ยังทำงานในโหมดเนทิฟเมื่อเทียบกับโหมดความเข้ากันได้

" IFSยังคงตั้งค่าเป็น." Eek หลังจากอ่านส่วนแรกนั่นก็สมเหตุสมผลแล้ว แต่ก่อนที่ฉันจะโพสต์คำถามนี้ฉันคงไม่คาดคิดเช่นนั้น
muru

1
นี่เป็นคำตอบสำหรับคำถามอื่น
schily

บางคำอธิบายเพิ่มเติมในคำตอบนี้จากปีที่ผ่านมา
Ti Strga

6

คำตอบของ @Gilles นั้นยอดเยี่ยมจริง ๆ เขาอธิบาย (โดยละเอียด) ปัญหาที่ซับซ้อน

อย่างไรก็ตามฉันเชื่อว่าคำตอบว่าทำไมคำสั่งนี้:

$ IFS=. printf "%s\n" $var
a.b.c

ทำงานเหมือนที่เป็นแนวคิดง่ายๆที่บรรทัดคำสั่งทั้งหมดจะถูกวิเคราะห์คำก่อนที่จะถูกดำเนินการ และแต่ละคำนั้นถูกประมวลผลโดยเชลล์
การมอบหมายเช่นIFS=.นั้นล่าช้า (ขั้นตอนที่ 4 คืออันสุดท้าย):

4.- การกำหนดตัวแปรแต่ละรายการจะถูกขยาย ...

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

$ IFS=. printf "%s\n" a.b.c           ## IFS=. goes to the environment.
a.b.c

ค่าของ$varถูกขยายด้วย IFS "เก่า" a.b.cก่อนที่คำสั่งprintfจะได้รับอาร์กิวเมนต์"%s\n"a.b.cและ

Eval

อาจมีการหน่วงเวลาหนึ่งระดับโดยeval:

$ IFS=. eval printf "'%s\n'" \$var
a
b
c

บรรทัดถูกแยกวิเคราะห์ (ครั้งที่ 1) และ 'IFS =' ถูกตั้งค่าเป็นสภาพแวดล้อมเช่นนี้:

$ printf '%s\n' $var

จากนั้นจะถูกแยกวิเคราะห์อีกครั้งสำหรับสิ่งนี้:

$ printf '%s\n' a b c

และดำเนินการกับสิ่งนี้:

a
b
c

ค่าของ$var(abc) ถูกแบ่งด้วยค่าของ IFS ที่ใช้งานอยู่:.คือแยกกับมูลค่าของไอเอฟเอในการใช้งาน:

สิ่งแวดล้อม

ส่วนที่ซับซ้อนและยุ่งยากคือสิ่งที่ใช้ได้ในสภาพแวดล้อมเมื่อ !!!

นั่นอธิบายได้ดีมากในส่วนแรกของคำตอบของ Gilles

พร้อมรายละเอียดเพิ่มเติม

เมื่อคำสั่งนี้ถูกดำเนินการ:

$ IFS=. arr=($var)

ค่าของ IFS จะถูกเก็บไว้ในสภาพแวดล้อมปัจจุบันใช่:

$ printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  <.> 

IFS สำหรับคำสั่งเดียว

แต่สามารถหลีกเลี่ยงได้: การตั้งค่า IFS สำหรับคำสั่งเดียว

$ IFS=. command eval arr\=\(\$var\)

$  printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  < 
> 

2

คำถามของคุณเกี่ยวกับ

var=a.b.c
IFS=. printf "%s\n" $var

เป็นกรณีมุม

นี่เป็นเพราะmacro expansionในคำสั่งเกิดขึ้นก่อนตัวแปรเชลล์IFS=.จะถูกตั้งค่า

ในคำอื่น ๆ : เมื่อ$varมีการขยายตัวก่อนหน้านี้IFSค่าการใช้งานแล้วมีการตั้งค่าIFS'.'

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.