看起来可能有问题openssh
。在bash
shell 中,如果我重定向stderr
到stdout
它,它就会永远阻塞,所以我必须KeyboardInterrupt
:
$ ssh -fTNF './config' -MS './sockd5/ctrl_socket' -i './keys/id_rsa' -l 'root' -p '22' 'example.com' 2>&1 | cat -A; echo OK
ControlSocket ./sockd5/ctrl_socket already exists, disabling multiplexing^M$
^C
没有重定向的相同命令可以正常工作:
$ ssh -fTNF './config' -MS './sockd/ctrl_socket' -i './keys/id_rsa' -l 'root' -p '22' 'example.com' | cat -A; echo OK
ControlSocket ./sockd/ctrl_socket already exists, disabling multiplexing
OK
为什么会发生这种情况?有解决方法吗?
答案1
当ssh -f
连接并验证到主机后“进入后台”时,它将继续保留其原始 stdin、stdout 和 stderr 的打开句柄,因此如果这些句柄通过管道连接到其他进程(如其 stdout + stderr在您的示例中cat -A
),它将具有使这些进程保持活动状态的效果,即使它们不再需要。
ssh
通过调用来守护进程自身daemon(3)
库函数,但它使用 来调用它noclose = 1
,从而阻止它从 重定向 stdin/stderr/stdout /dev/null
。
这在最近版本中得到了部分修复openssh
(对于主控制进程——标准输入和标准输出2010年,标准错误2016年;对于会话进程——标准输出2017年),但如果您必须运行较旧的 ssh,或者需要它也停止坚持 stderr,唯一的“解决方案”可能是使用 hack LD_PRELOAD
,用一个包装器覆盖该daemon(3)
函数,该包装器用noclose = 0
.
$ cat daemon-force-close.c
#define _GNU_SOURCE
#include <unistd.h>
#include <dlfcn.h>
#include <err.h>
int daemon(int nochdir, int noclose){
static int (*orig)(int, int);
if(!orig && !(*(void**)&orig = dlsym(RTLD_NEXT, "daemon")))
errx(1, "%s", dlerror());
return orig(nochdir, 0);
}
$ cc -shared -Wall -O2 daemon-force-close.c -ldl -o daemon-force-close.so
$ LD_PRELOAD=./daemon-force-close.so \
ssh -Nf dummy@localhost -MS ./ctlsock 2>&1 | cat -A
dummy@localhost's password:
$
[no Ctrl-C needed]
$ ssh -S ~/w/c/ctlsock dummy@localhost
Last login: Tue May 28 21:04:26 2019 from ::1
...
答案2
为什么会发生这种情况?
在 shell 到达 之前,需要cat
终止echo
。 由于 存在,因此会“永久阻塞” cat
。
有解决方法吗?
在 Bash 中,我会使用进程替换来运行cat
,不会阻塞。有了cat
out of the way,echo OK
只有当它确实没问题时(使用&&
或$?
)才很容易。例子:
ssh -f … > >(cat -A) 2>&1 && echo OK; echo "The script goes on."
现在在进入后台cat
之前和之后都可以工作ssh
,但是脚本一旦ssh
执行就会继续(或者一旦ssh
失败而不进入后台)。