我写了一个小脚本来自动提取/提交/推送一些个人数据。这个脚本在我的笔记本电脑(版本1.8.3.2-1)上工作得很好,但在我的服务器上,git pull
命令失败并显示信号13.我运行的是版本1.7.9.5-1(这些都是ubuntu,但是服务器是12.04.4 LTS对比笔记本电脑上的13.10),我使用ppa:git-core repo将git更新为1.9.0-1~ppa0~precision1。这仍然会给我的脚本带来同样的问题。可能的复杂性是,笔记本电脑从服务器上的裸仓库通过ssh拉出,而服务器从相同的裸仓库(到其他地方的工作副本)拉出但不使用ssh,它只有本地路径。
我可以使用以下命令在终端中重现问题:
> git pull origin master | read msg; echo ${msg}
First, rewinding head to replay your work on top of it...
error: git-pull died of signal 13
这似乎是获取提交并检查它,但是不更新主分支并使repo离开分支:
> git status
# Not currently on any branch.
nothing to commit (working directory clean)
切换回主人会发出警告:
> git checkout master
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
7220b8f Commit message
If you want to keep them by creating a new branch, this may be a good time
to do so with:
git branch new_branch_name 7220b8f5e2648ae49d3e3095e8bf942dfc41421c
Switched to branch 'master'
认为这个问题是因为stdout是一个管道,但是stderr是一个tty,我试过了:
> git pull origin master 2>&1 | read msg; echo ${msg}
但这根本不做任何事情,并使${msg}
为空。
工作是什么:
> git pull --quiet origin master 2>&1 | read msg; echo ${msg}
> git pull --quiet origin master
> git pull origin master
所有这些都会获取提交和更新主服务器。我想在某些情况下捕获git-pull的输出,所以当我可以修复回购时,我想知道为什么会发生这种情况。
那么为什么我不能捕获git-pull的输出,为什么它会让repo处于这样的状态呢?
答案 0 :(得分:7)
首先,你最大的问题是shell事物,它与git pull
本身没什么关系。让我们做一些微不足道的事情:
$ echo foo | read msg; echo $msg
$
为什么$msg
在这里为空?答案与管道,解析和子shell有关。
shell命令的基本语法是一系列"管道"由一系列分号和/或换行符分隔。那就是:
a | b; c
这与解析大致相同:
(a | b); c
或完全相同:
a | b
c
具体来说,b
部分绑定到a
部分,c
部分后来绑定。当然,添加显式括号会导致使用子shell,因此您可能会本能地意识到,如果b
部分是read
命令,则c
部分会赢得' t使变量可用,因为该设置仅影响子shell,而不影响外壳。
唉,删除括号无济于事。确实,这会在主shell中运行a
部分 - 但是为了读取管道的输出,b
部分仍然运行一个子壳。 (事实上,如果没有一些棘手的优化,当你使用括号时,b
部分在子子shell中运行:显式调用的子shell的子shell。)
这就是在管道到部件退出后无法访问$msg
的原因:任何管道的右侧总是在子壳中运行。
一切都不会丢失:考虑:
$ echo foo | { read msg; echo $msg; }
foo
$
这里的技巧是使用$msg
在子shell中运行整个序列。 (括号也有效,语法稍微不那么笨拙:echo foo | (read msg; echo $msg)
。)
让我们回到最初的尝试,看看有关修补的内容,例如:
git pull origin master | { read msg; echo ${msg}; }
这可能会做同样的事情:
First, rewinding head to replay your work on top of it...
error: git-pull died of signal 13
虽然确切的行为取决于很多东西。 (特别是"倒带"消息本身表示您已配置拉动以运行rebase。)
signal 13
部分告诉我们git-pull
收到SIGPIPE
错误:写入损坏的管道。破碎管可能是什么?答案应该是显而易见的,因为有一个管道用命令本身盯着我们:
git pull origin master | ...
管道"断裂"当读者 - 右手边read msg
- 在作者(LHS或git pull ...
)完成之前退出,然后作者写下新的东西。所以这表明git pull
正在写多行输出,因为read
命令读取一行然后退出(或者,在修改后的管道中,读取一行,将其写入stdout,并且然后退出)。
如果您想捕获输出,则需要捕获所有:
git pull origin master | while read msg; do ...; done
例如。 (这不再需要大括号或括号,因为while <list> do <list> done
序列作为单个语句进行解析。)或者:
git pull origin master > /tmp/script.$$
允许您在原始shell进程中读取捕获文件/tmp/script.$$
1 的内容。
not currently on any branch
状态发生是因为rebase
在中间被中断(由于管道错误)。 Rebase的工作原理是暂时离开分支,在新的匿名(未命名的&#34; not-on-a-branch&#34;)分支上累积新的提交,然后移动原始分支标签,以便新的和匿名的现在命名为branch,并且放弃了先前命名的分支。添加--quiet
会停止所有输出,以便read msg
等待整个git pull
序列完成(之后read
失败,因为毕竟没有输出。)< / p>
1 这真的应该使用mktemp
;以上仅供参考。
答案 1 :(得分:3)
注意:Git 2.2中的一个新功能(2014年11月)可能会对此问题产生影响,并避免出现任何信号问题(可能不完全符合您的情况,但更常见)。
请参阅commit 7559a1b中的Patrick Reynolds (piki
):
SIGPIPE
阻止和忽略信号 - 但未捕获信号 - 在
exec
之间继承 一些信号处理行为不稳定的呼叫者可以在SIGPIPE
被阻止或忽略的情况下调用git,甚至是非确定性的。 当SIGPIPE被阻止或被忽略时,几个git命令可以无限期地运行,忽略EPIPE
来自write()
次调用的返回,即使调用它们的进程已经消失。 我们的具体案例涉及到一个读取有限数量差异数据的脚本的git diff-tree
输出管道。在一个理想的世界中,绝不会在
SIGPIPE
被阻止或忽略的情况下调用git 但在现实世界中,包括Perl,Apache和Unicorn在内的几个真正潜在的调用者有时会产生忽略SIGPIPE
的子进程。强化git来应对这个错误比在每个潜在的父进程中清理它更容易,更有效率。
将SIGPIPE的处理方式恢复为默认值,这正是我们所期望的。