如何终止后台/分离的 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】:

感谢pgreppkillps | 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 会话)消失了,无需使用锤子(killkillallpkill 等)。

将其带回您的用例...

您正在建立您希望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 问题的极简方法,配置中只需要 ControlMasterControlPath。它只是将 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 会话?的主要内容,如果未能解决你的问题,请参考以下文章

如何强制从另一个 SSH 会话中分离屏幕?

应用程序终止时的 NSURLSession 后台会话回调

如何在系统(unix/Linux)后台运行java程序?

保持程序终止后的硬件句柄

如何在Linux中使用Shell脚本终止用户会话?

如何终止或停止 C++ 中的分离线程?