การอ่านบรรทัดจากไฟล์ที่มี bash: for vs. while


36

ฉันพยายามอ่านไฟล์ข้อความและทำบางอย่างกับแต่ละบรรทัดโดยใช้ bash script

ดังนั้นฉันมีรายการที่มีลักษณะเช่นนี้:

server1
server2
server3
server4

ฉันคิดว่าฉันสามารถวนรอบนี้โดยใช้ขณะที่ห่วงเช่น:

while read server; do
  ssh $server "uname -a"
done < /home/kenny/list_of_servers.txt

ขณะที่ลูปหยุดหลังจากเรียกใช้ 1 ครั้งดังนั้นจึงรันuname -aบนเซิร์ฟเวอร์1 เท่านั้น

อย่างไรก็ตามด้วย for for loop ที่ใช้ cat มันทำงานได้ดี:

for server in $(cat /home/kenny/list_of_servers.txt) ; do
  ssh $server "uname -a"
done

ยิ่งทำให้งงงันกับฉันก็คือสิ่งนี้ยังใช้งานได้:

while read server; do
  echo $server
done < /home/kenny/list_of_servers.txt

ทำไมตัวอย่างแรกของฉันหยุดหลังจากการทำซ้ำครั้งแรก

คำตอบ:


25

การforวนซ้ำทำได้ดีที่นี่ แต่โปรดทราบว่านี่เป็นเพราะไฟล์มีชื่อเครื่องซึ่งไม่มีอักขระช่องว่างหรืออักขระกลม for x in $(cat file); do …ไม่ทำงานเพื่อย้ำข้ามบรรทัดfileโดยทั่วไปเพราะเชลล์แยกเอาต์พุตแรกออกจากคำสั่งcat fileที่ใดก็ตามที่มีช่องว่างจากนั้นปฏิบัติต่อแต่ละคำว่าเป็นรูปแบบกลมดังนั้นจึง\[?*ขยายเพิ่มเติม คุณสามารถทำให้for x in $(cat file)ปลอดภัยถ้าคุณทำงานกับมัน:

set -f
IFS='
'
for x in $(cat file); do 

การอ่านที่เกี่ยวข้อง: วนลูปผ่านไฟล์ที่มีช่องว่างในชื่อหรือไม่ ; ฉันจะอ่านทีละบรรทัดจากตัวแปรใน bash ได้อย่างไร ; ทำไมถึงwhile IFS= readใช้บ่อย ๆ แทนที่จะเป็นIFS=; while read..? โปรดทราบว่าเมื่อใช้ไวยากรณ์ปลอดภัยในการอ่านบรรทัดคือwhile readwhile IFS= read -r line; do …

ทีนี้ลองมาดูว่าเกิดอะไรขึ้นกับwhile readความพยายามของคุณ การเปลี่ยนเส้นทางจากไฟล์รายการเซิร์ฟเวอร์ใช้กับการวนซ้ำทั้งหมด ดังนั้นเมื่อsshรันแล้วอินพุตมาตรฐานจะมาจากไฟล์นั้น ไคลเอ็นต์ ssh ไม่ทราบว่าเมื่อใดที่แอปพลิเคชันระยะไกลอาจต้องการอ่านจากอินพุตมาตรฐาน ดังนั้นทันทีที่ไคลเอ็นต์ ssh สังเกตเห็นอินพุตบางตัวมันจะส่งอินพุตนั้นไปยังด้านรีโมต เซิร์ฟเวอร์ ssh นั้นพร้อมที่จะป้อนข้อมูลอินพุตนั้นไปยังคำสั่งรีโมตหากต้องการ ในกรณีของคุณคำสั่งระยะไกลจะไม่อ่านอินพุตใด ๆ ดังนั้นข้อมูลจะถูกยกเลิก แต่ฝั่งไคลเอ็นต์ไม่ทราบอะไรเลย ความพยายามในการechoทำงานของคุณเพราะechoไม่เคยอ่านอินพุตใด ๆ แต่จะปล่อยอินพุตมาตรฐานไว้เพียงอย่างเดียว

มีหลายวิธีที่คุณสามารถหลีกเลี่ยงปัญหานี้ได้ คุณสามารถบอก ssh ไม่ให้อ่านจากอินพุตมาตรฐานพร้อม-nตัวเลือก

while read server; do
  ssh -n $server "uname -a"
done < /home/kenny/list_of_servers.txt

-nตัวเลือกในความเป็นจริงบอกจะเปลี่ยนเส้นทางการป้อนข้อมูลจากssh /dev/nullคุณสามารถทำได้ที่ระดับเชลล์และมันจะใช้ได้กับคำสั่งใด ๆ

while read server; do
  ssh $server "uname -a" </dev/null
done < /home/kenny/list_of_servers.txt

วิธีการดึงดูดการป้อนข้อมูลหลีกเลี่ยงการ SSH มาจากไฟล์ที่จะนำการเปลี่ยนเส้นทางในคำสั่ง:read while read server </home/kenny/list_of_servers.txt; do …สิ่งนี้จะไม่ทำงานเนื่องจากจะทำให้ไฟล์เปิดขึ้นอีกครั้งในแต่ละครั้งที่readคำสั่งถูกเรียกใช้งาน (ดังนั้นจึงจะอ่านบรรทัดแรกของไฟล์ซ้ำไปซ้ำมา) การเปลี่ยนเส้นทางจะต้องอยู่ในขณะที่ลูปเพื่อให้ไฟล์ถูกเปิดหนึ่งครั้งในช่วงระยะเวลาของลูป

โซลูชันทั่วไปคือการจัดเตรียมอินพุตให้กับลูปบนตัวให้คำอธิบายไฟล์นอกเหนือจากอินพุตมาตรฐาน เชลล์มีโครงสร้างเพื่อข้ามฟากอินพุตและเอาต์พุตจากหมายเลข descriptor หนึ่งไปยังอีก ที่นี่เราเปิดไฟล์บน file descriptor 3 และเปลี่ยนทิศทางreadอินพุตมาตรฐานของคำสั่งจาก file descriptor 3 ไคลเอ็นต์ ssh จะละเว้น open descriptor ที่ไม่ได้มาตรฐานดังนั้นทั้งหมดจึงเป็นไปด้วยดี

while read server <&3; do
  ssh $server "uname -a"
done 3</home/kenny/list_of_servers.txt

ใน bash readคำสั่งมีตัวเลือกเฉพาะให้อ่านจาก file descriptor อื่นดังนั้นคุณสามารถเขียนread -u3 serverได้

การอ่านที่เกี่ยวข้อง: ตัวอธิบายไฟล์ & การสคริปต์เชลล์ ; คุณจะใช้ตัวอธิบายไฟล์เพิ่มเติมเมื่อใด


5
ชายคนนี้แลกเปลี่ยนแน่นอนว่าแตกต่างจากเซิร์ฟเวอร์ผิดพลาด ผู้คนที่นี่ออกนอกเส้นทางเพื่อไม่เพียง แต่ตอบ แต่ยังให้ความรู้ด้วย ขอบคุณมาก.
Kenny Rasschaert

26

คุณควรใช้ไม่ได้while forวิธีที่จะหลีกเลี่ยงคำสั่งที่กลืนกินอินพุตมาตรฐานในการวนซ้ำนั้นก็คือการใช้ตัวอธิบายไฟล์อื่น:

while read -u 9 server; do
  ssh $server "uname -a"
done 9< /home/kenny/list_of_servers.txt

สำหรับข้อมูลเพิ่มเติมhelp [r]ead( จริงๆ ) และบทความอื่นอธิบายว่าทำไม


1
น่าสนใจฉันไม่เคยใช้ไฟล์อธิบายมาก่อน อะไร-uธงทำอย่างไร ฉันไม่แน่ใจในสิ่งที่หน้าคนฉันควรจะมองมัน
Kenny Rasschaert

คำสั่ง read เป็นส่วนหนึ่งของเชลล์ bash ดังนั้นจึงอยู่ใน man page ของ bash ในหัวข้อ "คำสั่ง SHELL BUILTIN COMMANDS" ในย่อหน้าการอ่าน คุณจะพบ "-u fd อ่านอินพุตจากไฟล์ descriptor fd" ในวรรคนี้
f4m8

6
อีกทางเลือกหนึ่งhelp read- helpเป็นคำสั่งที่จะได้รับเทียบเท่าของmanหน้าเปลือกหอย
l0b0

22

ในรหัสแรกของคุณsshจะ“ขโมย” การ STDIN whileจาก เพิ่ม-nตัวเลือกsshเพื่อหลีกเลี่ยง จากman ssh:

-n     Redirects stdin from /dev/null (actually, prevents reading from stdin).

ตกลงมีความคิดว่าทำไมมันไม่เกิดขึ้นกับ for loop?
Kenny Rasschaert

7
เนื่องจากการforวนซ้ำได้รับข้อมูลเป็นพารามิเตอร์ไม่ใช่อินพุต
จัดการ

1
คุณครับคุณสุภาพบุรุษและนักวิชาการ
Kenny Rasschaert

0

การจัดการอินพุตมาตรฐานเริ่มต้นของsshท่อระบายน้ำที่เหลืออยู่จากห่วงขณะที่

เพื่อหลีกเลี่ยงปัญหานี้ให้แก้ไขที่คำสั่งที่เป็นปัญหาอ่านอินพุตมาตรฐานจาก หากไม่จำเป็นต้องส่งอินพุตมาตรฐานไปยังคำสั่งให้อ่านอินพุตมาตรฐานจาก/dev/nullอุปกรณ์พิเศษ

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