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。
谁能解释一下:
- 为什么
%x()
挂起而 system()
不挂起?
- 为什么删除
2>&1
会有所不同?
这是 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
.
之外没有令人满意的解决方案
我正在使用 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。
谁能解释一下:
- 为什么
%x()
挂起而system()
不挂起? - 为什么删除
2>&1
会有所不同?
这是 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
.