2010-07-13 2 views
11

Я хочу начать пару заданий на разных машинах с помощью ssh. Если пользователь прерывает основной скрипт, я хочу изящно закрыть все задания.Запуск процесса по ssh с использованием bash, а затем его уничтожение по sigint

Вот небольшой пример того, что я пытаюсь сделать:

#!/bin/bash 
trap "aborted" SIGINT SIGTERM 
aborted() { 
    kill -SIGTERM $bash2_pid 
    exit 
} 

ssh -t remote_machine /foo/bar.sh & 
bash2_pid=$! 
wait 

Однако процесс bar.sh все еще работает на удаленной машине. Если я делаю те же команды в терминальном окне, он отключает процесс на удаленном хосте.

Есть ли простой способ сделать это, когда я запускаю скрипт bash? Или мне нужно заставить его войти в систему на удаленном компьютере, найти нужный процесс и убить его таким образом?

редактировать: Похоже, я должен идти с опцией B, убивая remotescript через другое соединение SSH

Так нет, я хочу знать, как я могу получить remotepid? Я пытался что-то вдоль линий:

remote_pid=$(ssh remote_machine '{ /foo/bar.sh & } ; echo $!') 

Это не работает, так как он блокирует.

Как подождать, пока будет напечатана переменная, а затем «отпустите» подпроцесс?

+1

Вы пробовали ловушку в своем удаленном скрипте? Возможно, SIGHUP? –

+0

Я попытался захватить все сигналы, о которых я могу думать без успеха ... Я предполагаю, что мне просто нужно снова отправить ssh на удаленный компьютер и запустить kill на скрипте, но тогда мне нужен pid удаленный процесс ... – getekha

+0

Попробуйте 'pkill', если так вы собираетесь идти. Сырой, но обычно эффективный. – bstpierre

ответ

16

Было бы предпочтительнее сохранить очистку, управляемую ssh, которая запускает процесс, а не перемещается для убийства со второй сессией ssh ​​позже.

Когда ssh подключен к вашему терминалу; он ведет себя довольно хорошо. Однако отсоедините его от вашего терминала, и он становится (как вы заметили) болью, чтобы сигнализировать или управлять удаленными процессами. Вы можете отключить связь, но не удаленные процессы.

Это дает вам один вариант: используйте ссылку в качестве способа для удаленного процесса получить уведомление о необходимости закрыть его. Самый чистый способ сделать это - использовать блокирующий ввод-вывод. Сделайте удаленный ввод для чтения из ssh и когда вы хотите, чтобы процесс был выключен; послать ему некоторые данные так, чтобы операция считывания на пульте дистанционного управления разблокирует и он может приступить к очистке:

command & read; kill $! 

Это то, что мы хотели бы работать на пульте дистанционного управления. Мы вызываем нашу команду, которую хотим запустить удаленно; мы читаем строку текста (блоки до тех пор, пока мы ее не получим), и когда мы закончим, подайте команду на завершение.

Чтобы отправить сигнал из нашего локального скрипта на удаленный компьютер, все, что нам нужно сделать, это отправить ему текст. К сожалению, Bash не дает вам много хороших вариантов. По крайней мере, если вы хотите быть совместимыми с bash < 4.0.

С Башем 4 мы можем использовать сопутствующие процессы:

coproc ssh [email protected] 'command & read; kill $!' 
trap 'echo >&"${COPROC[1]}"' EXIT 
... 

Теперь, когда выходит локальный скрипт (не ловушка на INT, TERM и т.д. Просто EXIT) он посылает новую строку в файл во втором элементе массива COPROC. Этот файл представляет собой трубу, которая подключена к sshstdin, эффективно маршрутизируя нашу линию до ssh. Удаленная команда считывает строку, завершает команду read и kill.

Перед тем, как bash 4, ситуация становится немного сложнее, так как у нас нет совместных процессов. В этом случае, мы должны сделать сами трубопроводы:

mkfifo /tmp/mysshcommand 
ssh [email protected] 'command & read; kill $!' < /tmp/mysshcommand & 
trap 'echo > /tmp/mysshcommand; rm /tmp/mysshcommand' EXIT 

Это должно работать в почти любой Баш версии.

+0

Это должно сработать. Сценарий должен работать на bash 3.2, поэтому мне нужно будет сделать свой собственный трубопровод. Обычный случай заключается в том, что удаленный процесс заканчивается нормально, поэтому я не могу полагаться на ловушку, чтобы сделать очистку от fifo. (возможно, если бы все они были в списке и делали очистку в конце программы, но я бы скорее удалил их, как только я закончил с ними). – getekha

+0

Этот вид работает, но проблема возникает, если программа заканчивается в обычном режиме: вызов чтения по-прежнему требует ввода для завершения. Любые идеи, как охватывать оба случая? – jkp

+0

@jkp: EXIT запускает, когда программа также заканчивается. Он должен охватывать оба случая. – lhunath

-1

Возможно, вы захотите рассмотреть возможность установки удаленной файловой системы и запустить сценарий из главного окна. Например, если ваше ядро ​​скомпилировано с предохранителем (можно проверить с нижеследующим):

/sbin/lsmod | grep -i fuse 

Вы можете затем смонтировать удаленную файловую систему с помощью следующей команды:

sshfs [email protected]_system: mount_point 

Теперь просто запустить скрипт в файле, расположенном в mount_point.

+0

. Цель настоящего скрипта - распределить работу на нескольких подчиненных машинах, поэтому это не сработает me .. – getekha

6

Попробуйте это:

ssh -tt host command </dev/null & 

Когда вы убиваете локальный процесс SSH, удаленный псевдотерминал закроется и SIGHUP будет отправлена ​​на удаленный процесс.

+0

Это не идеально подходит для сценариев, особенно при связывании сценария с ssh, это заставит его печатать выходные строки за строкой, как если бы это был интерактивный терминал. –

0

Раствор для Баш 3.2:

mkfifo /tmp/mysshcommand 
ssh [email protected] 'command & read; kill $!' < /tmp/mysshcommand & 
trap 'echo > /tmp/mysshcommand; rm /tmp/mysshcommand' EXIT 

не работает. Команда ssh не находится в списке ps на машине «клиент». Только после того, как я повторю что-то в трубе, он появится в списке процессов клиентской машины. Процесс, который появляется на «серверной» машине, будет просто самой командой, а не частью чтения/уничтожения.

Написание снова в трубу не завершает процесс.

Подводя итог, мне нужно записать в трубу команду для запуска, и если я снова напишу, она не уничтожит удалённую команду, как и ожидалось.

1

Реферирование ответ на lhunath и https://unix.stackexchange.com/questions/71205/background-process-pipe-input я придумал этот сценарий

run.sh:

#/bin/bash 
log="log"                     
eval "[email protected]" \&                    
PID=$!                      
echo "running" "[email protected]" "in PID $PID"> $log             
{ (cat <&3 3<&- >/dev/null; kill $PID; echo "killed" >> $log) & } 3<&0        
trap "echo EXIT >> $log" EXIT                
wait $PID 

Разница в том, что эта версия убивает процесс, когда соединение закрывается, но и возвращает код выхода команды при ее завершении.

$ ssh localhost ./run.sh true; echo $?; cat log 
0 
running true in PID 19247 
EXIT 

$ ssh localhost ./run.sh false; echo $?; cat log 
1 
running false in PID 19298 
EXIT 

$ ssh localhost ./run.sh sleep 99; echo $?; cat log 
^C130 
running sleep 99 in PID 20499 
killed 
EXIT 

$ ssh localhost ./run.sh sleep 2; echo $?; cat log 
0 
running sleep 2 in PID 20556 
EXIT 

Для однострочника:

ssh localhost "sleep 99 & PID=\$!; { (cat <&3 3<&- >/dev/null; kill \$PID) & } 3<&0; wait \$PID" 

Для удобства:

HUP_KILL="& PID=\$!; { (cat <&3 3<&- >/dev/null; kill \$PID) & } 3<&0; wait \$PID" 
ssh localhost "sleep 99 $HUP_KILL" 

Примечание: убить 0 может быть предпочтительным, чтобы убить $ PID в зависимости от поведения, необходимого в отношении породившей дочерние процессы. Вы также можете убить -HUP или kill -INT, если хотите.

Обновление: Дополнительный канал управления заданиями лучше, чем чтение из stdin.

ssh -n -R9002:localhost:8001 -L8001:localhost:9001 localhost ./test.sh sleep 2 

Установка режима управления заданиями и контролировать канал управления заданиями:

set -m 
trap "kill %1 %2 %3" EXIT 
(sleep infinity | netcat -l 127.0.0.1 9001) & 
(netcat -d 127.0.0.1 9002; kill -INT $$) & 
"[email protected]" & 
wait %3 

Наконец, вот другой подход и ссылка на ошибку, поданной OpenSSH: https://bugzilla.mindrot.org/show_bug.cgi?id=396#c14

Это лучший путь, который я нашел для этого. Вы хотите что-то на стороне сервера, которое пытается прочитать stdin, а затем убивает группу процессов, когда это не удается, но вы также хотите, чтобы stdin на стороне клиента блокировался до тех пор, пока процесс на стороне сервера не будет завершен и не оставит длительные процессы, такие как < (спать бесконечность).

ssh localhost "sleep 99 < <(cat; kill -INT 0)" <&1 

Это на самом деле не кажется, перенаправить стандартный вывод в любом месте, но он делает функцию как блокирующий вход и избегает захватив нажатия клавиш.