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

时间:2015-03-02 23:40:49

标签: ruby git shell ssh

我正在使用Ruby。我想弄清楚为什么捆绑工具rake release会在git push步骤上挂起,同时也会discussed inconclusively here

我已将其缩小到这行代码:

    `git push 2>&1`

我可以通过在IRB中运行相同的代码行来重现该问题。

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

检查进程列表显示子进程有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,这实际上解决了这个问题。

但是,这让我很困扰。我宁愿不必禁用ControlMaster只是为了让Ruby的反引号操作符满意。

任何人都可以解释:

  • 为什么%x()会挂起但system()不挂?
  • 为什么删除2>&1会有所作为?

这是在Mac OS X Yosemite上使用Ruby 2.2.0。

1 个答案:

答案 0 :(得分:3)

想出来:

  

为什么%x()挂起但系统()不挂?

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

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

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

  

为什么删除2>& 1会有所作为?

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

不幸的是,OpenSSH的这种行为没有显示出被改变的迹象,因此除了禁用ControlPersist之外,没有令人满意的解决方案。