การใช้ while loop เพื่อ ssh ไปยังเซิร์ฟเวอร์หลายเครื่อง


75

ฉันมีไฟล์servers.txtพร้อมรายการเซิร์ฟเวอร์:

server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

เมื่อฉันอ่านไฟล์ทีละบรรทัดด้วยwhileและสะท้อนแต่ละบรรทัดทำงานได้ตามที่คาดหวัง พิมพ์ทุกบรรทัด

$ while read HOST ; do echo $HOST ; done < servers.txt
server1.mydomain.com
server2.mydomain.com
server3.mydomain.com

อย่างไรก็ตามเมื่อฉันต้องการ ssh ไปยังเซิร์ฟเวอร์ทั้งหมดและรันคำสั่งทันใดนั้นwhileลูปของฉันก็หยุดทำงาน:

$ while read HOST ; do ssh $HOST "uname -a" ; done < servers.txt
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

นี่จะเชื่อมต่อกับเซิร์ฟเวอร์แรกในรายการเท่านั้นไม่ใช่ทั้งหมด ฉันไม่เข้าใจว่าเกิดอะไรขึ้นที่นี่ ใครช่วยอธิบายหน่อยได้ไหม?

นี่เป็นคนแปลกหน้าเนื่องจากการใช้การforวนซ้ำทำได้ดี:

$ for HOST in $(cat servers.txt ) ; do ssh $HOST "uname -a" ; done
Linux server1 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server2 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux
Linux server3 2.6.30.4-1 #1 SMP Wed Aug 12 19:55:12 EDT 2009 i686 GNU/Linux

มันจะต้องเป็นสิ่งที่เฉพาะเจาะจงsshเพราะคำสั่งอื่น ๆ ทำงานได้ดีเช่นping:

$ while read HOST ; do ping -c 1 $HOST ; done < servers.txt

4
การใช้งานansible
Matt

คำตอบ:


98

ssh กำลังอ่านส่วนที่เหลือของอินพุตมาตรฐานของคุณ

while read HOST ; do … ; done < servers.txt

readอ่านจาก stdin การ<เปลี่ยนเส้นทาง stdin จากไฟล์

น่าเสียดายที่คำสั่งที่คุณพยายามเรียกใช้ยังอ่าน stdin ดังนั้นจึงทำให้การกินไฟล์ส่วนที่เหลือของคุณช้าลง คุณสามารถดูได้อย่างชัดเจนด้วย:

$ while read HOST ; do echo start $HOST end; cat; done < servers.txt 
start server1.mydomain.com end
server2.mydomain.com
server3.mydomain.com

ขอให้สังเกตว่าcatกิน (และสะท้อน) สองบรรทัดที่เหลืออยู่อย่างไร (อ่านเสร็จตามที่คาดไว้แต่ละบรรทัดจะมี "เริ่มต้น" และ "สิ้นสุด" รอบ ๆ โฮสต์)

ทำไมถึงforทำงาน

forบรรทัดของคุณไม่เปลี่ยนเส้นทางไปยัง stdin (อันที่จริงแล้วมันจะอ่านเนื้อหาทั้งหมดของservers.txtไฟล์ไปยังหน่วยความจำก่อนการทำซ้ำครั้งแรก) ดังนั้นsshยังคงอ่าน stdin ของมันจากเทอร์มินัล (หรืออาจจะไม่มีอะไรขึ้นอยู่กับว่าสคริปต์ของคุณถูกเรียก)

วิธีการแก้

อย่างน้อยใน bash คุณสามารถreadใช้ file descriptor อื่นได้

while read -u10 HOST ; do ssh $HOST "uname -a" ; done 10< servers.txt
#          ^^^^                                       ^^

ควรจะทำงาน 10เป็นเพียงหมายเลขไฟล์ที่ฉันเลือก 0, 1 และ 2 มีความหมายที่กำหนดไว้และโดยทั่วไปการเปิดไฟล์จะเริ่มต้นจากหมายเลขที่มีอยู่ครั้งแรก (ดังนั้น 3 คือถัดจากที่จะใช้) 10 จึงสูงพอที่จะออกไปให้ไกล แต่ต่ำพอที่จะอยู่ในขีด จำกัด ของกระสุนบางนัด บวกกับจำนวนรอบที่ดี ...

ทางเลือกโซลูชันที่ 1: -n

เมื่อMcNisseชี้ให้เห็นในคำตอบของเขา / เธอลูกค้า OpenSSH มี-nตัวเลือกที่จะป้องกันไม่ให้อ่าน stdin วิธีนี้ใช้ได้ดีในกรณีเฉพาะsshแต่แน่นอนว่าคำสั่งอื่นอาจขาดได้ - โซลูชันอื่นทำงานได้โดยไม่คำนึงว่าคำสั่งใดกำลังใช้ stdin อยู่

ทางเลือกโซลูชันที่ 2: การเปลี่ยนเส้นทางที่สอง

เห็นได้ชัดว่าคุณสามารถ (ในขณะที่ฉันลองมันใช้งานได้ในเวอร์ชัน Bash อย่างน้อยฉัน ... ) ทำการเปลี่ยนเส้นทางครั้งที่สองซึ่งมีลักษณะดังนี้:

while read HOST ; do ssh $HOST "uname -a" < /dev/null; done < servers.txt

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


1
หมายเลข10มาจากไหน
Martin Vegter

2
@MartinVegter ฉันทำมันขึ้นมา 0/1/2 คือ stdin, stdout และ stderr คุณสามารถเลือกหมายเลขใดก็ได้ที่คุณชอบ - ทุบตีช่วยให้คุณสามารถสูงได้ถึงขีด จำกัด ระบบปฏิบัติการ เปลือกหอยอื่น ๆ อาจ จำกัด คุณน้อยลง ...
derobert

@MartinVegter ฉันได้แก้ไขเพื่อตอบความคิดเห็นของคุณทั้งสอง
derobert

อีกทางเลือกหนึ่ง: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- สิ่งนี้ทำให้ file descriptor 3 เป็นข้อมูลสำรองของ stdin ดั้งเดิมจากนั้นใช้เป็นข้อมูล stsh ของ ssh จากนั้นปิด FD สำรองข้อมูลเมื่อเสร็จสิ้น สิ่งนี้ใช้ได้กับเปลือกที่รองรับ POSIX
ริชาร์ดแฮนเซน

8
-uตัวเลือกที่ไม่ได้รับการสนับสนุนจาก POSIX และจึงไม่ควรนำมาใช้สำหรับ#!/bin/shสคริปต์; ใช้read HOST <&10แทน นอกจากนี้ POSIX ต้องการเพียงเชลล์เพื่อรองรับตัวอธิบายไฟล์ 0 ถึง 9 ดังนั้นจึง10<servers.txtไม่สามารถใช้งานได้หากสคริปต์ต้องสอดคล้องอย่างเคร่งครัด
Richard Hansen

28

ในฐานะที่เป็น derobert อธิบายอ่านของคุณsshstdin

ในการเปลี่ยนพฤติกรรมนี้คุณสามารถเพิ่ม -n no ssh เพื่อป้องกันไม่ให้อ่าน stdin

ssh -n $HOST "uname -a"

10

คุณอาจต้องการจริงจะดีกว่าการใช้psshจากขนาน SSHโครงการ

pssh -h $hostfile -t $timeout -i $commands

-iหมายถึงการโต้ตอบ pssh ยังมาพร้อมกับ SCP แบบขนานและ rsync แบบขนาน สิ่งที่ดีคือมันทำงานแบบอะซิงโครนัสและจะทำงานหลายเธรดตามที่คุณขอ ค่าเริ่มต้น (ไม่ใช่ -i / แบบโต้ตอบ) คือการส่งออกไปยังไดเรกทอรีที่แยกต่างหากสำหรับ stdout / stderr ซึ่งจะทำโดย $ outputdir / $ ชื่อโฮสต์


3

หากคุณพบว่าตัวเองทำงานประเภทนี้ค่อนข้างบ่อยคุณควรลองใช้ผ้า

ติดตั้งผ้าตามคำแนะนำส่วนใหญ่คุณเพียงแค่ต้องsudo apt-get install fabric

สร้างไฟล์ชื่อfabfile.pyด้วยรหัสต่อไปนี้:

from fabric.api import env, run

env.hosts = ['server1.mydomain.com',
             'server1.mydomain.com',
             'server1.mydomain.com']

def mytask():
    run('uname -a')

จากนั้นเรียกใช้fab mytaskจะให้ผลลัพธ์ที่คุณต้องการ


1

มันง่ายกว่าโดยใช้คำสั่งเช่นนี้:

for f in `cat servers.txt`; do ssh $f uname -a; done

ฉันมักจะทำสิ่งนี้:

for f in `cat servers.txt`; do echo "### $f ###"; ssh $f uname -a; done

echoคือการดูว่าเซิร์ฟเวอร์จะติดอยู่หรือไม่สามารถเชื่อมต่อกับมัน


1

เนื่องจาก ssh commnand รับสตรีมทั้งหมดจากอินพุตมาตรฐานป้อนโดยคำสั่ง while

คุณสามารถใช้ไพพ์เพื่อสลับ stdin ของ ssh ไปยังแหล่งอื่น:

echo "" | ssh ...

ตัวอย่าง:

while read HOST ; do echo "" | ssh $HOST "uname -a" ; done < servers.txt

อินพุต stdin ของคำสั่ง ssh ทั้งหมดในขณะที่ลูปจะต้องเปลี่ยนเป็นแหล่งอื่น

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