Использование цикла while для 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съели (и повторили) оставшиеся две строки. (Если бы read сделал это, как ожидалось, каждая строка имела бы «начало» и «конец» вокруг хоста.)

Почему forработает?

Ваша forлиния не перенаправляет на стандартный ввод. (На самом деле, он читает все содержимое servers.txtфайла в память перед первой итерацией). Так что sshпродолжает читать его стандартный вывод из терминала (или, возможно, ничего, в зависимости от того, как называется ваш скрипт).

Решение

По крайней мере, в 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, но, конечно, в других командах этого может не быть - другие решения работают независимо от того, какая команда потребляет ваш стандартный ввод.

Альтернативное решение 2: второе перенаправление

Видимо, вы можете (как я уже пробовал, это работает в моей версии Bash, по крайней мере ...) сделать второе перенаправление, которое выглядит примерно так:

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

Вы можете использовать это с любой командой, но это будет трудно, если вы действительно хотите, чтобы ввод терминала передавался команде.

derobert
источник
1
откуда берется номер 10?
Мартин Вегтер
2
@MartinVegter Я сделал это. 0/1/2 - это stdin, stdout и stderr. Вы можете выбрать любое понравившееся вам число - bash позволяет подняться довольно высоко, возможно, до предела ОС. Другие снаряды могут ограничить вас меньшим количеством ...
Дероберт
@MartinVegter Я отредактировал, чтобы ответить на оба ваших комментария.
Дероберт
Другая альтернатива: exec 3<&0; while read HOST; do ssh $HOST "uname -a" <&3; done <servers.txt; exec 3<&- это делает файловый дескриптор 3 резервной копией исходного stdin, затем использует его для sd sdin, а затем закрывает резервную копию FD, когда это будет сделано. Это работает на любой POSIX-совместимой оболочке.
Ричард Хансен
8
Эта -uопция не поддерживается POSIX и поэтому не должна использоваться для #!/bin/shсценариев; используйте read HOST <&10вместо Кроме того, POSIX требует, чтобы оболочки поддерживали файловые дескрипторы от 0 до 9, поэтому 10<servers.txtне могут использоваться, если сценарий должен строго соответствовать.
Ричард Хансен
28

Как описывает Дероберт, sshчитает stdin.

Чтобы изменить это поведение, вы можете добавить -n no ssh, чтобы он не читал стандартный ввод.

ssh -n $HOST "uname -a"
McNisse
источник
10

Возможно, вам лучше использовать pssh из проекта Parallels-SSH .

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

-iозначает интерактивный. pssh также поставляется с параллельным scp и параллельным rsync. Что приятно, так это то, что он работает асинхронно и будет запускать столько потоков, сколько вы просите. По умолчанию (не -i / интерактивный) выводится в отдельные каталоги для stdout / stderr, что делается с помощью $ outputdir / $ hostname.

infowolfe
источник
3

Если вы обнаружите, что делаете это довольно часто, вам следует попробовать Fabric

Установите 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даст вам результат, который вы хотите.

number5
источник
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 в цикле while должен быть переключен на другой источник.

Али ИССА
источник