system("git push 2>&1") 工作正常,但 %x(git push 2>&1) 挂起。为什么?

system("git push 2>&1") works fine, but %x(git push 2>&1) hangs. Why?

我正在使用 Ruby。我想弄清楚为什么捆绑器的 rake release 挂在 git push 步骤上,还有 discussed inconclusively here.

我已经缩小到这行代码挂起:

    `git push 2>&1`

我可以通过 运行 IRB 中的同一行代码重现问题。

神秘的是底层 git push 实际上执行了,但由于某种原因 Ruby 从未收到 return 状态。它只是无限期地等待子进程。

检查进程列表显示子进程具有 Z+(僵尸?)状态:

    UID   PID  PPID   C STIME   TTY           TIME CMD   USER       PGID   SESS JOBC STAT   TT 
    501 23397  3757   0  1:44PM ttys001    0:00.54 irb   mbrictson 23397      0    1 S+   s001 
    501 26035 23397   0  2:06PM ttys001    0:00.00 (sh)  mbrictson 23397      0    1 Z+   s001 

显然,git push 运行只是在我的 shell 中找到。只是在通过 Ruby 使用它挂起的反引号调用它时。

此外,这很好用:

    system("git push 2>&1") # => true

这(即没有输出重定向)也能正常工作!

    `git push` # => "Everything up-to-date"

部分问题显然是 ControlMaster auto 在我的 ~/.ssh/config 中。当执行 git push 时,这会导致在后台生成一个新的控制连接进程。也许 %x(git push 2>&1) 正在等待这个后台进程退出?如果我在我的 SSH 配置中禁用 ControlMaster,这实际上可以解决问题。

不过,这让我很困扰。我宁愿不必仅仅为了让 Ruby 的反引号操作员高兴而禁用 ControlMaster。

谁能解释一下:

这是 Mac OS X Yosemite 和 Ruby 2.2.0.

想通了:

Why does %x() hang but system() does not?

%x()等待完全读取子进程的输出; system() 不关心输出。

According to this bug report,OpenSSH 中的 ControlPersist 设置导致 stderr 在主连接的生命周期内保持打开状态。在我的 SSH 配置中,我有 ControlPersist 5m,果然,%x() 在最终完成之前挂起整整 5 分钟。

这不会影响 system() 因为系统不会等待输出。

Why does removing 2>&1 make a difference?

如上所述,SSH 主连接使 stderr 保持打开状态。它显然关闭了标准输出。由于 stdout 已关闭,%x(git push) 立即完成,因为在 stdout 上没有什么可等待的。当 2>&1 添加到命令时,这会导致 stderr 被重定向到 stdout。由于 stderr 被主连接保持打开状态,这反过来导致 stdout 保持打开状态。 %x 等待标准输出并挂起。

不幸的是,OpenSSH 的这种行为没有任何改变的迹象,因此除了禁用 ControlPersist.

之外没有令人满意的解决方案