我正在使用 Ruby。我想弄清楚为什么 bundler 的 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"
部分问题显然是在我的 ~/.ssh/config
中的 ControlMaster auto
。当执行 git push
时,这会导致在后台生成一个新的控制连接进程。也许 %x(git push 2>&1)
正在等待这个后台进程退出?如果我在我的 SSH 配置中禁用 ControlMaster,这实际上可以解决问题。
不过,这让我很困扰。我宁愿不必仅仅为了让 Ruby 的反引号运算符满意而禁用 ControlMaster。
谁能解释一下:
- 为什么
%x()
挂起而system()
没有挂起? - 为什么删除
2>&1
会有所不同?
这是在带有 Ruby 2.2.0 的 Mac OS X Yosemite 上运行的。
最佳答案
想通了:
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
之外没有令人满意的解决方案。
关于ruby - system ("git push 2>&1") 工作正常,但 %x(git push 2>&1) 挂起。为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28821507/