如何终止后台/分离的ssh会话?

68

我正在使用Synergy程序和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脚本也可以工作,但我现在还想通过一个鼠标点击来终止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 ... 组合,必须有更简单的方法。)

12个回答

86
尊敬的pgreppkillps | awk的用户们,我谨代表所有it技术人员向您致敬,但是有一种方法更好。
考虑到如果您依赖于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进入后台,保持隧道开启。但如果隧道消失了,我就会陷入困境,如果我想找到它,我必须解析我的进程列表,并确保我有“正确”的ssh(以防我意外启动了多个看起来相似的ssh)。

相反,如果我想管理多个连接,我使用SSH的ControlMaster配置选项,以及控制的-O命令行选项。例如,在我的~/.ssh/config文件中加入以下内容:

host officefirewall colocatedserver
    ControlMaster auto
    ControlPath ~/.ssh/cm_sockets/%r@%h:%p

以上的ssh命令运行后,会在~/.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处理的,而Control{Master,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

2
@GeorgiosPolitis,谢谢!我不反对!:-)可惜,我的回答是在问题提出5年后才给出的。也许随着时间的推移,会有更多人点赞这个回答。 - ghoti
@ghoti - 我认为这个答案也可能受到TL;DR综合症的影响。我建议重新调整你的答案,将“将其带回您的用例”部分移到顶部,可能就在第二段下面(显示这是更好的方法)。 - Randall
此外,虽然您可以在.ssh/config文件中拥有所有这些选项,但作为对OP问题的极简主义方法,配置中只需要ControlMasterControlPath。 这只是将OP的-O检查更改为ssh -l otherUser -O check OtherHost - Randall
5
@Randall,我不同意。虽然原问题可能只需要一个简约回答,只针对 Synergy 客户端和服务器,但是我的回答是在 OP 最后一次登录的将近5年后编写的。使用情况并不重要,因为它对 OP 没有帮助。这样的问题通过提供更通用的内容可以更好地服务于社区,从而让更广泛的受众受益。 - ghoti

74

简要概述:不会起作用。

我的第一个想法是,您需要在后台启动进程,并使用$!获取它们的PID。

像这样的模式

some_program &
some_pid=$!
wait $some_pid

这可能符合你的需求...但是这样SSH就不会在前台询问密码了。

那么,也许你需要另外一种方法。 ssh -f 可能会生成一个新进程,而无论如何,您的shell都无法知道它。理想情况下,ssh本身应该提供一种将其PID写入某个文件的方式。


是的,没错。他不能让一个进程脱离shell的控制并期望$!被填充。 - TheBonsai
10
正如作者所提到的,ssh -f 无法起作用,因为它会生成一个新的进程。我不知道为什么人们一直点赞这个。 - Konstantin Pereiaslov
38
我猜这是因为人们(比如我)在谷歌上搜索如何在Bash中获取最近启动的进程的PID,找到了这个页面,跳到第一个答案,然后就会说:“哦,对了,$!,我忘了。” 然后我们会点赞并离开,不知道也不关心作者的意图。 - Joe Pinsonault
2
@JoePinsonault 我觉得问题的标题有问题。 - Gray Fox

21

我刚刚看到这个帖子,想提一下Linux工具"pidof":

$ pidof init
1

那么这是在寻找所有名为“init”或第一个进程的进程,对吗? - rogerdpack
2
man:“Pidof查找命名程序的进程ID(PID)。”默认情况下,它将查找所有匹配的进程,但您可以使用“-s”选项仅选择第一个匹配项。另一个有用的选项是“-x”(也包括脚本),它还将查找运行命名脚本的shell的PID。退出代码告诉您实用程序是否找到了任何内容。 - zrathen

16
您可以使用lsof命令显示本地监听端口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"

13

我不想在命令的末尾添加&,因为如果控制台窗口关闭,连接将会断开...所以最终我用了ps-grep-awk-sed组合。

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中,但我现在太懒了)


1
只想补充一下,如果你有pgrep,我认为你可以使用像pgrep -f -x 'ssh -f -N -L localhost'这样的东西。 - emil.p.stanchev

9
你可以省略-f,这会让它在后台运行。然后使用eval命令并强制将其放置到后台。你可以获得pid,确保在eval语句内加上&标记。
eval "ssh -N -L localhost:12345:otherHost:12345 otherUser@OtherHost & " 
tunnelpid=$!

1
这个解决方案正是我想要的,当我遇到这个问题时,为了同时使用多个ssh隧道。但是如果你使用它,请不要忘记在eval之后添加sleep 5,以便ssh有5秒钟来建立连接。 - Konstantin Pereiaslov
8
抱歉,为什么您需要再次使用eval?$! 不只是获取最后一个后台进程吗?谢谢。 - inger

4
另一种选择是使用pgrep查找最新的ssh进程的PID。
ssh -fNTL 8073:localhost:873 otherUser@OtherHost
tunnelPID=$(pgrep -n -x ssh)
synergyc localhost
kill -HUP $tunnelPID

4

这更像是synergyc(以及大多数其他试图将自己变成守护进程的程序)的特殊情况。使用$!会起作用,但是synergyc在执行过程中会进行clone()系统调用,这将给它一个新的PID,而不是bash认为它具有的那个PID。如果您想绕过此问题以便使用$!,那么可以告诉synergyc保持在前台,然后将其放入后台。

synergyc -f -n mydesktop remoteip &
synergypid=$!

synergyc还有一些其他功能,比如自动重启,如果您想管理它,可能需要关闭这些功能。


3

基于 @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 返回时关闭。尽管该解决方案缺乏适当的错误报告。


2
您可以使用以下命令查找绑定到本地端口的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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接