เหตุใด `ในขณะที่ IFS = read` ใช้บ่อยๆแทนที่จะเป็น 'IFS =; ในขณะที่อ่าน ..


81

ดูเหมือนว่าการปฏิบัติตามปกติจะทำให้การตั้งค่าของ IFS อยู่นอกวงขณะที่เพื่อไม่ให้ซ้ำการตั้งค่าสำหรับการวนซ้ำแต่ละครั้ง ... นี่เป็นเพียงลักษณะ "ลิงดูลิงทำ" เป็นนิสัยเพราะมันเป็นลิงนี้จนกระทั่ง ฉันอ่านman readหรือว่าฉันขาดกับดักบางอย่าง (หรือโจ่งแจ้งชัดเจน) ที่นี่?

คำตอบ:


82

กับดักคือ

IFS=; while read..

ชุดIFSสำหรับสภาพแวดล้อมของเปลือกนอกวงในขณะที่

while IFS= read

กำหนดใหม่เฉพาะสำหรับการreadเรียกใช้ (ยกเว้นในเชลล์เป้าหมาย) คุณสามารถตรวจสอบว่าการทำลูปเช่น

while IFS= read xxx; ... done

หลังจากecho "blabalbla $IFS ooooooo"พิมพ์วนซ้ำ

blabalbla
 ooooooo

ในขณะที่หลังจาก

IFS=; read xxx; ... done

IFS การเข้าพักนิยามใหม่: ตอนนี้echo "blabalbla $IFS ooooooo"พิมพ์

blabalbla  ooooooo

IFS=$' \t\n'ดังนั้นถ้าคุณใช้แบบฟอร์มที่สองคุณต้องจำไว้เพื่อรีเซ็ต:


ส่วนที่สองของคำถามนี้ถูกรวมที่นี่ดังนั้นฉันได้ลบคำตอบที่เกี่ยวข้องออกจากที่นี่


โอเคดูเหมือนว่า 'กับดัก' ที่เป็นไปได้คือละเลยที่จะรีเซ็ต IFS ภายนอก ... แต่ฉันสงสัยว่ายังมีสิ่งอื่นอีกที่กำลังทำงานอยู่หรือไม่ ... ฉันกำลังทดสอบสิ่งต่าง ๆ ที่นี่ ขอให้สังเกตว่าการตั้งค่า IFS ในขณะที่รายการคำสั่งของคำสั่งทำงานในลักษณะที่แตกต่างกันขึ้นอยู่กับว่ามีการติดตามตามด้วยโคลอนหรือไม่ ฉันไม่เข้าใจพฤติกรรมนี้ (และ) และตอนนี้ฉันสงสัยว่ามีการพิจารณาพิเศษที่เกี่ยวข้องในระดับนี้ ... เช่น while IFS=X readไม่แยกที่Xแต่while IFS=X; readไม่ ...
Peter.O

(คุณหมายถึงกึ่งลำไส้ใหญ่ใช่ไหม?) อย่างที่สองก็whileไม่ได้ทำให้ความรู้สึกมาก - เงื่อนไขสำหรับwhile ปลายที่อัฒภาคว่าดังนั้นจึงไม่มีห่วงจริง ... readจะกลายเป็นเพียงแค่คำสั่งแรกภายในวงหนึ่งองค์ประกอบ ... หรือไม่ ? ถ้าอย่างdoนั้น ..
rozcietrzewiacz

1
ไม่รอ - คุณพูดถูกคุณสามารถมีหลายคำสั่งในwhileเงื่อนไข (ก่อนหน้าdo)
rozcietrzewiacz

โอ้ .. แน่นอนคุณสามารถมีพวกเขา ... ตามที่คุณรับรู้ ... แต่พวกเขาดูเหมือนจะไม่ชอบเซมิโคลอน ... (และลูปจะวนลูป ad-infinitum จนกว่าคำสั่งสุดท้ายจะส่งกลับไม่ใช่ - รหัสทางออกzero) ... ตอนนี้ฉันสงสัยว่ากับดักอยู่ในภาคอื่นโดยสิ้นเชิงหรือไม่ ที่เข้าใจว่าขณะที่รายการคำสั่งทำงานอย่างไรเช่น ทำไมถึงใช้IFS=งานได้ แต่IFS=Xไม่ ... (หรือบางทีฉันอาจเคยเจอเรื่องนี้ซักพักนึง ... ต้องมีช่วงพักดื่มกาแฟ :)
เตอร์

1
$ rozcietrzewiacz .. โอ๊ะโอ ... ฉันไม่ได้สังเกตการอัปเดตของคุณเมื่อฉันย้ายการอัปเดตของฉัน (ดังที่ได้กล่าวไว้ในความคิดเห็นก่อนหน้า) .. มันดูน่าสนใจและมันเริ่มมีเหตุผล ... แต่ถึงคืนหนึ่ง - นกเหมือนฉันมันสายมาก ... (ฉันเพิ่งได้ยินนกตอนเช้า:) ... ที่กล่าวว่าฉันรวบรวมและอ่านตัวอย่างของคุณ ... ฉันคิดว่าฉันได้รับมันจริง ๆ แล้วฉัน ' ฉันแน่ใจว่าคุณได้รับแล้ว แต่ฉันต้องนอนหลับ :) ... นี่มันเกือบจะเป็นยูเรก้า! ช่วงเวลา ... ขอบคุณ
Peter.O

45

ลองดูตัวอย่างด้วยข้อความอินพุตที่สร้างขึ้นอย่างระมัดระวัง:

text=' hello  world\
foo\bar'

นั่นคือสองบรรทัดเริ่มต้นครั้งแรกด้วยช่องว่างและลงท้ายด้วยแบ็กสแลช ก่อนอื่นมาดูสิ่งที่เกิดขึ้นโดยไม่มีข้อควรระวังread(แต่ใช้printf '%s\n' "$text"เพื่อพิมพ์อย่างระมัดระวัง$textโดยไม่เสี่ยงต่อการขยายตัว) (ด้านล่าง$ ‌เป็นพรอมต์เชลล์)

$ printf '%s\n' "$text" |
  while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]

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

$ printf '%s\n' "$text" |
  while read -r line; do printf '%s\n' "[$line]"; done
[hello  world\]
[foo\bar]

ดีกว่าเรามีสองบรรทัดตามที่คาดไว้ ทั้งสองบรรทัดเกือบจะมีเนื้อหาที่ต้องการ: เว้นวรรคระหว่างhelloและworldถูกเก็บรักษาไว้เพราะอยู่ในlineตัวแปร ในทางตรงกันข้ามพื้นที่เริ่มต้นถูกกินหมด นั่นเป็นเพราะreadอ่านคำได้มากเท่าที่คุณส่งผ่านตัวแปรยกเว้นว่าตัวแปรสุดท้ายมีส่วนที่เหลือของบรรทัด - แต่มันยังคงขึ้นต้นด้วยคำแรกนั่นคือช่องว่างเริ่มต้นจะถูกทิ้ง

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

$ printf '%s\n' "$text" |
  while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello  world\]
[foo\bar]

โปรดทราบว่าเรากำหนดเป็นIFS พิเศษสำหรับช่วงเวลาของการreadมีอยู่แล้วภายใน IFS= read -r lineชุดตัวแปรสภาพแวดล้อมIFS(เพื่อค่าว่าง) readโดยเฉพาะสำหรับการดำเนินการของ นี่คืออินสแตนซ์ของไวยากรณ์คำสั่งทั่วไปทั่วไป: ลำดับ (อาจว่างเปล่า) ของการกำหนดตัวแปรตามด้วยชื่อคำสั่งและอาร์กิวเมนต์ (เช่นคุณสามารถโยนในการเปลี่ยนเส้นทางได้ทุกจุด) เนื่องจากreadเป็นบิวด์อินตัวแปรจึงไม่สิ้นสุดในสภาพแวดล้อมของกระบวนการภายนอก อย่างไรก็ตามค่าของ$IFSคือสิ่งที่เรากำหนดไว้ที่นั่นตราบใดที่readกำลังดำเนินการ¹ โปรดทราบว่าreadไม่ใช่การสร้างขึ้นเป็นพิเศษดังนั้นการมอบหมายจะคงอยู่ในระยะเวลาที่กำหนดเท่านั้น

ดังนั้นเราจึงไม่ควรเปลี่ยนค่าของIFSคำแนะนำอื่น ๆ ที่อาจเชื่อถือได้ รหัสนี้จะทำงานไม่ว่าสิ่งที่รหัสรอบได้มีการกำหนดIFSที่จะเริ่มต้นและจะไม่ก่อให้เกิดปัญหาใด ๆ IFSถ้ารหัสภายในห่วงอาศัย

ตัดกับโค้ดขนาดสั้นนี้ซึ่งค้นหาไฟล์ในพา ธ ที่คั่นด้วยโคลอน รายการชื่อไฟล์จะอ่านจากไฟล์หนึ่งชื่อไฟล์ต่อบรรทัด

IFS=":"; set -f
while IFS= read -r name; do
  for dir in $PATH; do
    ## At this point, "$IFS" is still ":"
    if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
  done
done <filenames.txt

ถ้าลูปwhile IFS=; read -r name; do …นั้นfor dir in $PATHจะไม่แยก$PATHเป็นส่วนประกอบที่คั่นด้วยโคลอน ถ้ารหัสเป็นIFS=; while read …มันจะชัดเจนยิ่งขึ้นที่IFSไม่ได้ตั้งค่าไว้:ในเนื้อหาของลูป

แน่นอนว่ามันจะเป็นไปได้ที่จะเรียกคืนค่าของหลังจากรันIFS readแต่นั่นจะต้องรู้ค่าก่อนหน้าซึ่งเป็นความพยายามพิเศษ IFS= readเป็นวิธีที่ง่าย (และสะดวกยังเป็นวิธีที่สั้นที่สุด)

¹ และถ้าreadถูกขัดจังหวะโดยสัญญาณที่ติดอยู่อาจเป็นไปได้ในขณะที่กับดักกำลังดำเนินการ - นี้ไม่ได้ระบุโดย POSIX และขึ้นอยู่กับเปลือกในทางปฏิบัติ


4
ขอบคุณ Gilles .. ไกด์นำเที่ยวที่ดีมาก .. (คุณหมายถึง 'set -f' หรือเปล่า?) .... ตอนนี้สำหรับผู้อ่านที่จะย้ำสิ่งที่พูดไปแล้วฉันอยากจะเน้นประเด็นที่มี ฉันมองมันผิดทาง สิ่งแรกและสำคัญที่สุดคือความจริงที่ว่าการสร้างwhile IFS= read(โดยไม่ต้องกึ่งหลัง=) ไม่ได้เป็นรูปแบบพิเศษของwhileหรือของIFSหรือของread.. การก่อสร้างเป็นเรื่องทั่วไป: เช่น anyvar=anyvalue anycommand. การขาด;หลังจากการตั้งค่าanyvarจะทำให้ขอบเขตของanyvar ท้องถิ่นที่จะanycommand.. ในขณะที่ - ทำ / ห่วงทำคือ 100% ที่ไม่เกี่ยวข้องany_varกับขอบเขตของท้องถิ่น
Peter.O

3

นอกเหนือจากการIFSกำหนดขอบเขตที่แตกต่าง(ชัดเจนแล้ว) ระหว่างwhile IFS='' read, IFS=''; while readและwhile IFS=''; readสำนวน (ต่อคำสั่ง vs สคริปต์ / การIFSกำหนดขอบเขตแบบกว้างเชลล์), บทเรียนนำกลับบ้านคือการที่คุณสูญเสียช่องว่างนำหน้าและต่อท้ายของอินพุตบรรทัดถ้าตัวแปร IFS ถูกตั้งค่าเป็น (ประกอบด้วย) พื้นที่

สิ่งนี้อาจมีผลกระทบร้ายแรงหากเส้นทางของไฟล์ถูกประมวลผล

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

ดูเพิ่มเติม: Bash, อ่านทีละบรรทัดจากไฟล์, ด้วย IFS

(
shopt -s nullglob
touch '  file with spaces   '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)

+1 การสาธิตที่ยอดเยี่ยมทำความสะอาดหลังจากด้วย 'rm * file * with * ช่องว่าง *'
2560

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