这是最奇怪的事情。
在脚本中,我启动 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,正如我所期望的那样,并且当这一系列命令在脚本中运行时确实是这样。
有人可以解释为什么会发生这种情况吗?我认为这可能是由于最初的
ssh
调用实际上分叉了另一个进程,但是为什么它在脚本内工作呢?这也让我怀疑
$!
在我的脚本中使用ssh
上面描述的方法来获取 PID 是否确实总是有效(尽管到目前为止已经有效)。这确实可靠吗?我觉得它比使用pgrep
...... “更干净”
答案1
shell的$!
变量只知道shell启动的进程的pid。正如您所怀疑的,ssh
使用-f
fork 自己的进程进行调用,以便它可以转到后台,因此整个进程树看起来像 [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 或其他东西来跟踪你的所有孩子。但你不是。