Sử dụng vòng lặp while để ssh tới nhiều máy chủ


75

Tôi có một tập tin servers.txt, với danh sách các máy chủ:

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

Khi tôi đọc từng dòng tệp whilevà lặp lại từng dòng, tất cả đều hoạt động như mong đợi. Tất cả các dòng được in.

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

Tuy nhiên, khi tôi muốn ssh tới tất cả các máy chủ và thực thi một lệnh, đột nhiên whilevòng lặp của tôi ngừng hoạt động:

$ 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

Điều này chỉ kết nối với máy chủ đầu tiên trong danh sách, không kết nối với tất cả chúng. Tôi không hiểu chuyện gì đang xảy ra ở đây. Ai đó có thể vui lòng giải thích?

Điều này thậm chí còn lạ, vì sử dụng forvòng lặp hoạt động tốt:

$ 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

Nó phải là một cái gì đó cụ thể ssh, bởi vì các lệnh khác hoạt động tốt, chẳng hạn như ping:

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

4
sử dụngansible
Matt

Câu trả lời:


98

ssh đang đọc phần còn lại của đầu vào tiêu chuẩn của bạn.

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

readđọc từ stdin. Các <chuyển hướng stdin từ một tập tin.

Thật không may, lệnh bạn đang cố chạy cũng đọc stdin, vì vậy nó sẽ ăn hết phần còn lại của tệp. Bạn có thể thấy nó rõ ràng với:

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

Chú ý cách catăn (và lặp lại) hai dòng còn lại. (Đã đọc xong như mong đợi, mỗi dòng sẽ có "bắt đầu" và "kết thúc" xung quanh máy chủ.)

Tại sao forlàm việc?

forDòng của bạn không chuyển hướng đến stdin. (Trong thực tế, nó đọc toàn bộ nội dung của servers.txttệp vào bộ nhớ trước khi lặp đầu tiên). Vì vậy, sshtiếp tục đọc stdin của nó từ thiết bị đầu cuối (hoặc có thể không có gì, tùy thuộc vào cách tập lệnh của bạn được gọi).

Giải pháp

Ít nhất là trong bash, bạn có thể readsử dụng một bộ mô tả tệp khác.

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

nên làm việc 10chỉ là một số tập tin tùy ý tôi chọn. 0, 1 và 2 có ý nghĩa xác định và thông thường việc mở tệp sẽ bắt đầu từ số có sẵn đầu tiên (vì vậy 3 là tiếp theo sẽ được sử dụng). 10 là đủ cao để tránh đường, nhưng đủ thấp để ở dưới giới hạn trong một số vỏ. Cộng với số tròn đẹp ...

Giải pháp thay thế 1: -n

Như McNisse chỉ ra trong câu trả lời của anh ấy / cô ấy , ứng dụng khách OpenSSH có một -ntùy chọn sẽ ngăn không cho nó đọc stdin. Điều này hoạt động tốt trong trường hợp cụ thể ssh, nhưng tất nhiên các lệnh khác có thể thiếu điều này, các giải pháp khác hoạt động bất kể lệnh nào đang ăn stdin của bạn.

Giải pháp thay thế 2: chuyển hướng thứ hai

Bạn rõ ràng có thể (như trong, tôi đã thử nó, nó hoạt động trong phiên bản Bash của tôi ít nhất ...) thực hiện chuyển hướng thứ hai, trông giống như thế này:

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

Bạn có thể sử dụng điều này với bất kỳ lệnh nào, nhưng sẽ rất khó nếu bạn thực sự muốn đầu vào đầu cuối đi vào lệnh.


1
số này 10đến từ đâu?
Martin Vegter

2
@MartinVegter Tôi đã làm nó lên. 0/1/2 là stdin, stdout và stderr. Bạn có thể chọn bất kỳ số nào bạn thích, bash cho phép bạn tăng khá cao, có thể đến giới hạn hệ điều hành. Các vỏ khác có thể giới hạn bạn ít hơn ...
derobert

@MartinVegter Tôi đã chỉnh sửa để trả lời cả hai nhận xét của bạn.
derobert

Một cách khác: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- Điều này làm cho bộ mô tả tệp 3 trở thành bản sao lưu của stdin gốc, sau đó sử dụng nó cho stdin của ssh, sau đó đóng FD sao lưu khi hoàn tất. Điều này hoạt động trên bất kỳ vỏ tương thích POSIX.
Richard Hansen

8
Các -utùy chọn không được hỗ trợ bởi POSIX, và do đó không nên được sử dụng cho #!/bin/shcác kịch bản; sử dụng read HOST <&10thay thế. Ngoài ra, POSIX chỉ yêu cầu hệ vỏ để hỗ trợ mô tả tệp từ 0 đến 9, do đó 10<servers.txtkhông thể được sử dụng nếu tập lệnh phải tuân thủ nghiêm ngặt.
Richard Hansen

28

Như derobert mô tả sshđọc của bạn stdin.

Để thay đổi hành vi này, bạn có thể thêm -n no ssh để ngăn nó đọc stdin.

ssh -n $HOST "uname -a"

10

Có lẽ bạn thực sự nên sử dụng pssh từ dự án ssh song song .

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

-icó nghĩa là tương tác. pssh cũng đi kèm với một scp song song và rsync song song. Điều tuyệt vời là nó chạy không đồng bộ và sẽ chạy nhiều luồng như bạn yêu cầu. Mặc định (không -i / tương tác) là xuất ra các thư mục riêng cho stdout / stderr, được thực hiện bởi $ outputdir / $ hostname.


3

Nếu bạn thấy mình thực hiện loại nhiệm vụ này khá thường xuyên, bạn nên thử Fabric

Cài đặt Fabric theo hướng dẫn , hầu hết các trường hợp bạn chỉ cầnsudo apt-get install fabric

Tạo một tệp có tên fabfile.pymã sau:

from fabric.api import env, run

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

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

Sau đó chạy fab mytasksẽ cho bạn kết quả mà bạn muốn.


1

Thật dễ dàng hơn khi sử dụng một lệnh như thế này:

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

Tôi thường làm như thế này:

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

Để echoxem máy chủ nào bị kẹt hoặc không thể kết nối với máy chủ đó.


1

Bởi vì một ssh commnand lấy tất cả luồng từ đầu vào tiêu chuẩn, được cung cấp bởi câu lệnh while,

Bạn có thể sử dụng một đường ống để chuyển stdin của ssh sang một nguồn khác:

echo "" | ssh ...

thí dụ :

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

đầu vào stdin của tất cả các lệnh ssh trong một vòng lặp while phải được chuyển sang nguồn khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.