我有一个包含这些内容的 check out 后和 merge 后 githook:
#!/bin/bash
# MIT © Sindre Sorhus - sindresorhus.com
set -eux
changed_files="$(git diff-tree -r --name-only --no-commit-id HEAD@{1} HEAD)"
check_run() {
echo "$changed_files" | grep --quiet "$1" && eval "$2"
}
echo ''
echo 'running git submodule update --init --recursive if .gitmodules has changed'
check_run .gitmodules "git submodule update --init --recursive"
echo ''
echo 'running npm install if package.json has changed'
check_run package.json "npm prune && npm install"
echo ''
echo 'running npm build:localhost'
npm run build:localhost
奇怪的是,如果 .gitmodules 没有变化,脚本就会结束而不是继续检查 package.json。 (它甚至不执行第 12 行之后的回显行)
删除 check_run 调用并只输入直接命令似乎工作正常。
删除 check_run .gitmodules "git submodule update --init --recursive"
也能正常工作。然而,下一行显示了相同的行为:check_run package.json "npm prune && npm install"
if package.json has not been changed
我是否遗漏了什么导致 check_run 在未找到第一个文件更改时结束脚本?
最佳答案
set -e
是这里的问题:-e
表示“如果失败则退出”,其中“失败”定义为“非零退出”。
我们在输出中看到,如最后几行:
+ check_run .gitmodules 'git submodule update --init --recursive'
+ echo ''
+ grep --quiet .gitmodules
(然后没有别的)。脚本在 grep --quiet
之后退出.
这是 check_run
的实际定义:
check_run() {
echo "$changed_files" | grep --quiet "$1" && eval "$2"
}
这解析为 <em>left</em> && <em>right</em>
左边 是 echo ... | grep ...
而对是eval "$2"
.
我们看到左边的部分运行了,右边的部分没有运行。这是我们需要了解一些关于 shell 的地方:即使是 -e
设置后,如果出现故障,它们不会立即退出,只要那是测试的一部分。1 所以这不是立即解决问题。
但还是有问题,因为<em>left</em> && <em>right</em>
有,作为它的退出状态,它运行的最后一个东西的退出状态。它运行的最后一件事是 left 管道,它是 echo ... | grep ...
.管道的退出状态是其最后一个组件2 的退出状态,即grep
。 .如果 grep 找到字符串(并且使用 --quiet
,也会抑制其输出),则 grep 退出为零,如果没有,则为 1,如果出现一般错误,则为 2,因此退出状态为 1。
因此,<em>left</em> && <em>right</em>
的退出状态也是 1。
因此,由于-e
生效,shell 退出!
解决方法是避免-e
(但这意味着如果其他事情意外失败,shell 会继续运行,所以这可能有点危险),或者确保 <em>left</em> && <em>right</em>
不会使 shell 退出。
后者有一种直接的方法:替换 <em>left</em> && <em>right</em>
与 if <em>left</em>; then <em>right</em>; fi
:
if echo "$changed_files" | grep --quiet "$1"; then
eval "$2"
fi
请注意,如果 eval "$2"
失败,shell 仍然会退出。
有一种不同且略微棘手的方法可以达到不同的效果:替换 <em>left</em> && <em>right</em>
与 <em>left</em> && <em>right</em> || true
. “and”表达式结合得更紧密,所以这意味着:
- 评估左边
- 如果成功(退出零),评估正确
- 如果
&&
失败(left 退出非零,或者 right 运行并且 right 退出非零),评估|| true
部分,退出 0。
因此:
echo "$changed_files" | grep --quiet "$1" && eval "$2" || true
总是退出 0,eval
-ing "$2"
当且仅当左侧失败(没有找到 grepped-for 表达式)。
如果你想要check_run
即使eval "$2"
也要耕种失败,使用第二个(|| true
)版本。如果你想要check_run
在 -e
下停止(并让整个 shell 退出)如果eval "$2"
失败,使用第一个(if ...; then
)版本。
1真正古老的 4BSD /bin/sh
,早在开源之前就有一个错误:-e
会在这些情况下使 shell 退出。 (我想我曾经自己修复过那个问题,但是当 4BSD 转向一个新的 sh 时,不是基于 Steve Bourne 的原始代码,这个错误根本就不存在了。)
2在 bash 中,您可以更详细地控制它。特别是,您可以在 $PIPESTATUS
中获取管道的每个 组件的状态。数组变量。
关于Git Hook 静默失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43878982/