预测先前启动的 SSH 命令的 PID

预测先前启动的 SSH 命令的 PID

这是最奇怪的事情。

在脚本中,我启动 SSH 隧道,如下所示:

ssh -o StrictHostkeyChecking=no -fND 8080 foo@bar

这将启动一个ssh进入后台的实例,并且脚本继续执行。接下来,我使用 bash 保存它的 PID(以便稍后杀死它)$!多变的。为了使其工作,我附加&ssh命令,即使它本身已经进入后台(否则$!不包含任何内容)。因此,例如以下脚本:

#!/bin/bash

ssh -o StrictHostkeyChecking=no -fND 8080 foo@bar &
echo $!
pgrep -f "ssh -o StrictHostkeyChecking=no -fND 8080 foo@bar"

输出

(some ssh output)
28062
28062

...正如预期的那样,是相同 PID 的两倍。但是,现在,当我执行这个确切的命令序列从终端来看,PID输出$!错误的(从某种意义上说,它是不是实例的 PID ssh)。从航站楼:

$ ssh -o StrictHostkeyChecking=no -fND 8080 foo@bar &
[1] 28178
(some ssh output)
$ echo $!
28178
$ pgrep -f "ssh -o StrictHostkeyChecking=no -fND 8080 foo@bar"
28181

它也不总是相隔 3 个数字。我还观察到 1 或 2 的差异。但它从来不是相同的 PID,正如我所期望的那样,并且当这一系列命令在脚本中运行时确实是这样。

  1. 有人可以解释为什么会发生这种情况吗?我认为这可能是由于最初的ssh调用实际上分叉了另一个进程,但是为什么它在脚本内工作呢?

  2. 这也让我怀疑$!在我的脚本中使用ssh上面描述的方法来获取 PID 是否确实总是有效(尽管到目前为止已经有效)。这确实可靠吗?我觉得它比使用pgrep...... “更干净”

答案1

shell的$!变量只知道shell启动的进程的pid。正如您所怀疑的,ssh使用-ffork 自己的进程进行调用,以便它可以转到后台,因此整个进程树看起来像 [1]:

shell
|
+--ssh<1> (pid is $!)
   |
   +--ssh<2> (pid is different)

ssh<1>调用后很快退出;因此, 中的值$!不太可能有用。它ssh<2>负责进行远程通信并为您建立隧道,可靠地获取其 PID 的唯一方法是检查进程表,就像您在pgrep[2] 中所做的那样。这里的方法pgrep可能是正确的。

至于为什么它在脚本中有效但不能交互,这可能是一个竞争条件。因为您将第一个进程ssh放在后台,所以 shell 和ssh正在同时执行,并ssh执行一些中等 CPU 密集型加密身份验证和一些网络往返。pgrep您在脚本中运行的内容很可能只是在ssh<1>fork 本身进入后台之前运行。要解决此问题,请pgrep稍后运行,可以通过sleep调用运行,也可以仅在稍后实际需要 PID 时调用它。

ssh[1]:从技术上讲,如果使用经典的双叉作为背景,可能会比这更复杂。那样的话,ssh两者之间就会有另一个短暂的过程。

[2]:除非你正在systemd使用 cgroup 或其他东西来跟踪你的所有孩子。但你不是。

相关内容