Використання циклу для 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
Метт

Відповіді:


98

ssh читає решту вашого стандартного вводу.

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

readчитає зі стдін. У <перенаправляє стандартний ввід з файлу.

На жаль, команда, яку ви намагаєтеся запустити, також читає 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використовувати інший дескриптор файлів.

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

повинні працювати. 10це лише довільний номер файлу, який я вибрав. 0, 1 і 2 мають визначені значення, і зазвичай відкриття файлів розпочнеться з першого доступного номера (тому 3 слід використовувати далі). Таким чином, 10 досить високий, щоб не виходити з дороги, але достатньо низький, щоб бути в деяких снарядах. Плюс його хороший круглий номер ...

Альтернативне рішення 1: -n

Як вказує Макнісс у своїй / її відповіді , у клієнта OpenSSH є -nможливість, яка не дозволить йому прочитати stdin. Це добре працює в конкретному випадку ssh, але, звичайно, інших команд цього може не вистачати - інші рішення працюють незалежно від того, яка команда їсть ваш stdin.

Альтернативне рішення 2: друге переадресація

Ви можете, мабуть, (як і я спробував це, він працює принаймні у моїй версії Bash ...) зробити друге перенаправлення, яке виглядає приблизно так:

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

Ви можете використовувати це з будь-якою командою, але це буде важко, якщо ви дійсно хочете, щоб термінал входив до команди.


1
звідки 10походить число ?
Мартін Вегтер

2
@MartinVegter Я це придумав. 0/1/2 є stdin, stdout та stderr. Ви можете вибрати будь-яке вподобане вам число - bash дозволяє перейти досить високо, ймовірно, до межі ОС. Інші снаряди можуть обмежити вас меншою кількістю ...
derobert

@MartinVegter Я редагував відповіді на обидва ваші коментарі.
дероберт

Інша альтернатива: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- Це робить дескриптор файлу 3 резервним копієм вихідного stdin, потім використовує його для stsh ssh, після чого закриває FD резервного копіювання, коли це завершено. Це працює на будь-якій сумісній з POSIX оболонці.
Річард Хансен

8
-uОпція не підтримується POSIX, і , таким чином , не повинні використовуватися для #!/bin/shсценаріїв; використовувати read HOST <&10замість цього. Також POSIX вимагає лише оболонок для підтримки дескрипторів файлів від 0 до 9, тому 10<servers.txtїх не можна використовувати, якщо сценарій повинен суворо відповідати.
Річард Хансен

28

Як описує derobert, sshчитає ваше stdin.

Щоб змінити цю поведінку, ви можете додати -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

Якщо ви досить часто робите подібне завдання, вам варто спробувати Fabric

Встановіть тканину, дотримуючись інструкцій , у більшості випадків вам просто потрібно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 команд через цикл час повинен бути переключений на інше джерело.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.