使用bash在ssh上启动进程,然后在sigint上将其杀死

时间:2010-07-13 08:01:48

标签: bash ssh signals

我想使用ssh在不同的机器上启动几个作业。如果用户然后中断主脚本,我想优雅地关闭所有作业。

以下是我正在尝试做的一个简短示例:

#!/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,通过另一个ssh连接杀死remotescript

所以我不想知道如何获得remotepid? 我尝试了一些类似的东西:

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

这不起作用,因为它会阻止。

如何等待变量打印然后“释放”子流程?

5 个答案:

答案 0 :(得分:17)

最好保持清理由启动进程的ssh管理,而不是稍后使用第二个ssh会话进入kill。

当ssh连接到您的终端时;它表现得很好。但是,将它从终端中分离出来(正如您所注意到的那样)会发出信号或管理远程进程的痛苦。您可以关闭链接,但不能关闭远程进程。

这为您提供了一个选项:使用链接作为远程进程获取其需要关闭的通知的方式。最简单的方法是使用阻塞I / O.从ssh进行远程读取输入以及何时需要关闭进程;发送一些数据,以便遥控器的读取操作解锁,并且可以继续进行清理:

command & read; kill $!

这就是我们想要在遥控器上运行的内容。我们调用我们想要远程运行的命令;我们读了一行文本(块直到我们收到一个),当我们完成后,发出命令终止信号。

要将信号从我们的本地脚本发送到遥控器,我们现在需要做的就是发送一行文字。不幸的是,Bash在这里没有给你很多好的选择。至少,如果你想与bash兼容,那就不是了。 4.0。

使用bash 4,我们可以使用协同进程:

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

现在,当本地脚本退出时(不要陷入INTTERM等等,只是EXIT)它会在第二个元素中向该文件发送一个新行。 COPROC数组。该文件是一个连接到ssh的{​​{1}}的管道,有效地将我们的行路由到stdin。 remote命令读取该行,结束命令的sshread

在bash之前,由于我们没有共同进程,所以事情变得有点困难。在这种情况下,我们需要自己做管道:

kill

这应该适用于任何bash版本。

答案 1 :(得分:6)

试试这个:

ssh -tt host command </dev/null &

当您终止本地ssh进程时,远程pty将关闭,SIGHUP将被发送到远程进程。

答案 2 :(得分:1)

引用lhunath和https://unix.stackexchange.com/questions/71205/background-process-pipe-input的答案我想出了这个脚本

run.sh:

#/bin/bash
log="log"                                                                                 
eval "$@" \&                                                                              
PID=$!                                                                                    
echo "running" "$@" "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"

注意:kill 0可能更适合杀死$ PID,具体取决于生成的子进程所需的行为。如果你愿意,你也可以杀死-HUP或杀死-INT。

更新: 辅助作业控制通道优于从标准输入读取。

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 $$) &
"$@" &
wait %3

最后,这是另一种方法和对openssh上提交的错误的引用: https://bugzilla.mindrot.org/show_bug.cgi?id=396#c14

这是我发现这样做的最好方法。您希望服务器端尝试读取stdin,然后在失败时终止进程组,但是您还希望客户端上的stdin阻塞,直到服务器端进程完成,并且不会留下像&lt;这样的延迟进程。 (睡眠无限)可能。

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

实际上似乎没有将stdout重定向到任何地方,但它确实起到阻塞输入的作用,并避免捕获击键。

答案 3 :(得分:0)

bash 3.2的解决方案:

mkfifo /tmp/mysshcommand
ssh user@host 'command & read; kill $!' < /tmp/mysshcommand &
trap 'echo > /tmp/mysshcommand; rm /tmp/mysshcommand' EXIT

不起作用。 ssh命令不在“客户端”机器上的ps列表中。只有在我将某些内容回传到管道后,它才会显示在客户端计算机的进程列表中。 “服务器”机器上显示的进程只是命令本身,而不是读取/终止部分。

再次写入管道不会终止该过程。

总结一下,我需要写入管道以便启动命令,如果我再次写入,它不会像预期的那样终止远程命令。

答案 4 :(得分:-1)

您可能需要考虑安装远程文件系统并从主框运行脚本。例如,如果您的内核是使用fuse编译的(可以查看以下内容):

/sbin/lsmod | grep -i fuse

然后,您可以使用以下命令安装远程文件系统:

sshfs user@remote_system: mount_point

现在只需在mount_point中的文件上运行脚本。