如何终止后台/分离的 ssh 会话?
Posted
技术标签:
【中文标题】如何终止后台/分离的 ssh 会话?【英文标题】:How do I kill a backgrounded/detached ssh session? 【发布时间】:2010-12-21 18:34:05 【问题描述】:我正在将程序协同与 ssh 隧道一起使用
它工作,我只需要打开一个控制台输入这两个命令:
ssh -f -N -L localhost:12345:otherHost:12345 otherUser@OtherHost
synergyc localhost
因为我很懒,我制作了一个 Bash 脚本,只需在图标上单击鼠标即可运行:
#!/bin/bash
ssh -f -N -L localhost:12345:otherHost:12345 otherUser@OtherHost
synergyc localhost
上面的 Bash-Script 也可以,但是现在我还想通过鼠标单击来杀死 synergy 和 ssh 隧道,所以我必须将 synergy 和 ssh 的 PID 保存到文件中以便以后杀死它们:
#!/bin/bash
mkdir -p /tmp/synergyPIDs || exit 1
rm -f /tmp/synergyPIDs/ssh || exit 1
rm -f /tmp/synergyPIDs/synergy || exit 1
[ ! -e /tmp/synergyPIDs/ssh ] || exit 1
[ ! -e /tmp/synergyPIDs/synergy ] || exit 1
ssh -f -N -L localhost:12345:otherHost:12345 otherUser@OtherHost
echo $! > /tmp/synergyPIDs/ssh
synergyc localhost
echo $! > /tmp/synergyPIDs/synergy
但是这个脚本的文件是空的。
如何获取 ssh 和 synergy 的 PID?
(我尽量避免ps aux | grep ... | awk ... | sed ...
组合,必须有更简单的方法。)
【问题讨论】:
【参考方案1】:快速总结:行不通。
我的第一个想法是,您需要在后台启动进程以使用$!
获取它们的 PID。
类似的模式
some_program &
some_pid=$!
wait $some_pid
可能会做您需要的事情...除了 ssh 将不再在前台询问密码。
那么,你可能需要一些不同的东西。 ssh -f
可能会产生一个新进程,你的 shell 无论如何都无法通过调用它来知道它。理想情况下,ssh 本身会提供一种将其 PID 写入某个文件的方法。
【讨论】:
是的,没错。他不能让进程自己脱离 shell 的控制,并期望一个填充的 $! 正如作者所说,它不适用于 ssh -f,因为它会产生一个新进程。我不知道,为什么人们一直在投票。 我想这是因为人们(像我一样)用谷歌搜索如何在 bash 中获取最后启动的进程的 PID,找到这个页面,跳到第一个答案,然后去“哦,对,$ !, 我忘了”。然后我们投赞成票,不知道也不关心作者想要什么。 @JoePinsonault 我怪问题的标题。【参考方案2】:感谢pgrep
、pkill
、ps | awk
等的用户,有一个很多更好的方法。
考虑一下,如果您依赖ps -aux | grep ...
来查找进程,则存在发生冲突的风险。您可能有一个不太可能的用例,但作为一般规则,这不是要走的路。
SSH 提供了一种管理和控制后台进程的机制。但就像许多 SSH 的东西一样,它是一个“高级”功能,很多人(似乎,从这里的其他答案看来)并不知道它的存在。
在我自己的用例中,我在家里有一个工作站,我想在它上面留下一个隧道,连接到我办公室内部网络上的 HTTP 代理,另一个让我可以快速访问公司上的管理界面- 定位的服务器。这就是您可以在家中创建基本隧道的方式:
$ ssh -fNT -L8888:proxyhost:8888 -R22222:localhost:22 officefirewall
$ ssh -fNT -L4431:www1:443 -L4432:www2:443 colocatedserver
这些会导致 ssh 自己后台运行,从而使隧道保持打开状态。但是如果隧道消失了,我就卡住了,如果我想找到它,我必须解析我的进程列表和 home 我有“正确”的 ssh(以防我不小心启动了多个看起来类似)。
相反,如果我想管理多个连接,我会使用 SSH 的 ControlMaster
配置选项以及 -O
命令行选项进行控制。例如,在我的~/.ssh/config
文件中使用以下内容,
host officefirewall colocatedserver
ControlMaster auto
ControlPath ~/.ssh/cm_sockets/%r@%h:%p
上面的 ssh 命令在运行时会将 spoor 留在 ~/.ssh/cm_sockets/
中,然后可以提供控制访问权限,例如:
$ ssh -O check officefirewall
Master running (pid=23980)
$ ssh -O exit officefirewall
Exit request sent.
$ ssh -O check officefirewall
Control socket connect(/home/ghoti/.ssh/cm_socket/ghoti@192.0.2.5:22): No such file or directory
此时,隧道(和控制 SSH 会话)消失了,无需使用锤子(kill
、killall
、pkill
等)。
将其带回您的用例...
您正在建立您希望syngergyc
与 TCP 端口 12345 上的syngergys
通信的隧道。为此,我将执行以下操作。
在您的~/.ssh/config
文件中添加一个条目:
Host otherHosttunnel
HostName otherHost
User otherUser
LocalForward 12345 otherHost:12345
RequestTTY no
ExitOnForwardFailure yes
ControlMaster auto
ControlPath ~/.ssh/cm_sockets/%r@%h:%p
请注意,命令行-L
选项使用LocalForward
关键字处理,并且包含 ControlMaster,Path 行以确保您在隧道建立后拥有控制权。
然后,您可以将 bash 脚本修改为如下内容:
#!/bin/bash
if ! ssh -f -N otherHosttunnel; then
echo "ERROR: couldn't start tunnel." >&2
exit 1
else
synergyc localhost
ssh -O exit otherHosttunnel
fi
-f
选项使隧道成为背景,在您的 ControlPath 上留下一个套接字,以便稍后关闭隧道。如果 ssh 失败(可能是由于网络错误或ExitOnForwardFailure
),则无需退出隧道,但如果它没有失败(else
),则启动 synergyc,然后关闭隧道退出。
您可能还想查看是否可以使用 SSH 选项 LocalCommand
从您的 ssh 配置文件中直接启动 synergyc
。
【讨论】:
@GeorgiosPolitis,谢谢!我不会反对! :-) 唉,我的回答是在提出问题 5 年后才给出的。随着时间的推移,也许会有更多的人对此投赞成票。 @ghoti - 我认为这个答案也可能患有 TL;DR 综合征。我建议修改您的答案,将“将其带回您的用例”部分移至顶部,也许就在第二段下方(表明这是一个更好的方法) 此外,虽然您当然可以在.ssh/config
文件中拥有所有这些选项,但作为 OP 问题的极简方法,配置中只需要 ControlMaster
和 ControlPath
。它只是将 OP 的 -O 检查更改为 ssh -l otherUser -O check OtherHost
@Randall,我不同意。虽然最初的问题将由仅针对 Synergy 客户端和服务器的简约答案提供,但我的答案是在 OP 上次登录后近 5 年编写的。用例并不重要,因为它对 OP 没有帮助。通过提供更通用的内容,这样的问题可以更好地为社区服务,更广泛的受众可以从中受益。【参考方案3】:
刚刚遇到这个帖子,想提一下“pidof” linux 实用程序:
$ pidof init
1
【讨论】:
所以这会查找所有名为“init”的进程或第一个进程,还是什么? man: "Pidof 找到指定程序的进程 ID (pids)"。默认情况下,它将查找所有匹配的进程,但您可以使用“-s”选项仅选择第一个匹配的进程。另一个有用的选项是“-x”(也是脚本),它还将查找运行命名脚本的 shell 的 pid。退出代码会告诉您该实用程序是否找到任何东西。【参考方案4】:你可以使用 lsof 来显示监听 localhost 上 12345 端口的进程的 pid:
lsof -t -i @localhost:12345 -sTCP:listen
例子:
PID=$(lsof -t -i @localhost:12345 -sTCP:listen)
lsof -t -i @localhost:12345 -sTCP:listen >/dev/null && echo "Port in use"
【讨论】:
【参考方案5】:好吧,我不想在命令末尾添加 &,因为如果控制台窗口关闭,连接将会中断......所以我最终得到了一个 ps-grep-awk-sed-combo
ssh -f -N -L localhost:12345:otherHost:12345 otherUser@otherHost
echo `ps aux | grep -F 'ssh -f -N -L localhost' | grep -v -F 'grep' | awk ' print $2 '` > /tmp/synergyPIDs/ssh
synergyc localhost
echo `ps aux | grep -F 'synergyc localhost' | grep -v -F 'grep' | awk ' print $2 '` > /tmp/synergyPIDs/synergy
(你可以将 grep 集成到 awk 中,但我现在太懒了)
【讨论】:
只想补充一点,如果你有 pgrep 可用,我认为你可以使用 pgrep -f -x 'ssh -f -N -L localhost' 之类的东西。【参考方案6】:您可以删除-f
,使其在后台运行,然后使用eval
运行它并自己将其强制到后台。
然后您可以获取pid
。确保将&
放在eval
语句中。
eval "ssh -N -L localhost:12345:otherHost:12345 otherUser@OtherHost & "
tunnelpid=$!
【讨论】:
这个解决方案实际上是我想要的,当遇到这个问题以便一次使用多个 ssh 隧道时。但是如果你用了,别忘了在eval
后面加sleep 5
给ssh 5秒的时间建立连接
对不起,为什么你又需要评估?不会$!干脆抢最后一个后台进程?谢谢【参考方案7】:
这更像是 synergyc(以及大多数其他试图自我守护的程序)的特殊情况。使用 $!会起作用,除了 synergyc 在执行期间会执行一个 clone() 系统调用,这将给它一个新的 PID,而不是 bash 认为它拥有的那个。如果您想解决这个问题以便可以使用 $!,那么您可以告诉 synergyc 留在前台,然后将其置于后台。
synergyc -f -n mydesktop remoteip &
synergypid=$!
synergyc 还执行其他一些操作,例如自动重启,如果您尝试管理它,您可能希望将其关闭。
【讨论】:
【参考方案8】:另一种选择是使用 pgrep 查找最新 ssh 进程的 PID
ssh -fNTL 8073:localhost:873 otherUser@OtherHost
tunnelPID=$(pgrep -n -x ssh)
synergyc localhost
kill -HUP $tunnelPID
【讨论】:
【参考方案9】:基于@ghoti 的非常好的回答,这里有一个更简单的脚本(用于测试),利用SSH 控制套接字,无需额外配置:
#!/bin/bash
if ssh -fN -MS /tmp/mysocket -L localhost:12345:otherHost:12345 otherUser@otherHost; then
synergyc localhost
ssh -S /tmp/mysocket -O exit otherHost
fi
synergyc
只有在隧道建立成功后才会启动,一旦synergyc
返回,隧道本身就会关闭。
尽管该解决方案缺乏适当的错误报告。
【讨论】:
【参考方案10】:您可以使用以下行查看绑定到本地端口的 ssh 进程:
netstat -tpln | grep 127\.0\.0\.1:12345 | awk 'print $7' | sed 's#/.*##'
它使用本地主机上的端口 12345/TCP 返回进程的 PID。所以您不必过滤来自ps
的所有ssh
结果。
如果您只需要检查,如果该端口已绑定,请使用:
netstat -tln | grep 127\.0\.0\.1:12345 >/dev/null 2>&1
如果没有绑定则返回1
,如果有人在监听此端口,则返回0
。
【讨论】:
【参考方案11】:这里有很多有趣的答案,但没有人提到 SSH 的手册页确实描述了这种确切的情况! (见TCP FORWARDING
部分)。而且他们提供的解决方案要简单得多:
ssh -fL 12345:localhost:12345 user@remoteserver sleep 10
synergyc localhost
现在详细介绍:
-
首先我们使用隧道启动 SSH;感谢
-f
,它将启动连接,然后才分叉到后台(与ssh ... &; pid=$!
的解决方案不同,其中ssh 被发送到后台并在创建隧道之前执行下一个命令之前)。在远程机器上它将运行sleep 10
,这将等待 10 秒然后结束。
在 10 秒内,我们应该启动我们想要的命令,在本例中为 synergyc localhost
。它将连接到隧道,然后 SSH 将知道该隧道正在使用中。
10 秒后,sleep 10
命令将完成。但是synergyc
仍在使用该隧道,因此 SSH 不会关闭底层连接,直到隧道被释放(即直到synergyc
关闭套接字)。
当synergyc
关闭时,它会释放隧道,而 SSH 又会自行终止,从而关闭连接。
这种方法的唯一缺点是,如果我们使用的程序由于某种原因会关闭并重新打开连接,那么 SSH 会在连接关闭后立即关闭隧道,并且程序将无法重新连接。如果这是一个问题,那么您应该使用@doak's answer 中描述的方法,该方法使用控制套接字正确终止 SSH 连接并使用-f
确保在 SSH 分叉到后台时创建隧道。
【讨论】:
这是一个很好的解决方案!非常干净简单,不涉及任何全局配置。以上是关于如何终止后台/分离的 ssh 会话?的主要内容,如果未能解决你的问题,请参考以下文章