我有一个结账后和合并后的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"
如果package.json尚未更改
是否有一些我遗漏的东西导致check_run在第一个文件更改未找到时结束脚本?
答案 0 :(得分:7)
此处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"
}
此解析为left && right
,其中左为echo ... | grep ...
,右为eval "$2"
。
我们看到左侧部分跑了而右侧部分没跑。这里我们需要了解一些关于shell的内容:即使设置了-e
,如果某些内容失败,它们也不会立即退出,只要某些内容是测试的一部分< / em>。 1 所以这不是问题所在。
但它仍然是问题,因为left && right
作为退出状态,它运行的最后一件事的退出状态。它运行的最后一件事是左管道,它是echo ... | grep ...
。管道的退出状态是其最后一个组件的退出状态, 2 ,即grep
。如果grep找到字符串(并且使用--quiet
,也会抑制其输出),则退出零;如果不是,则返回1,并且一般错误为2,因此退出状态为1.
因此,left && right
的退出状态也为1。
因此,由于-e
生效,shell退出了!
治愈要么是为了避免-e
(但这意味着如果其他事情意外失败,那么外壳会砰的一声,这可能会有点危险),或者确保left && right
做到了不要让shell退出。
有一种直接的方法来执行后者:将left && right
替换为if left; then right; fi
:
if echo "$changed_files" | grep --quiet "$1"; then
eval "$2"
fi
请注意,如果eval "$2"
失败,shell仍会退出。
使用不同的效果执行此操作有一种不同且稍微棘手的方法:将left && right
替换为left && right || true
。 “和”表达式绑定得更紧密,因此这意味着:
&&
失败(左退出非零,或正确运行且正确退出非零),请评估|| 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
失败,您希望eval "$2"
停在if ...; then
下,并使用第一个(/bin/sh
)版本。
1 真正古老的4BSD -e
早在它开源之前就有一个错误:$PIPESTATUS
会让shell退出这些案例。 (我想我自己一次修了那个,但是当4BSD进入一个新的时候,不是基于史蒂夫伯恩的原始代码,那个错误根本就不存在。)
2 在bash中,您可以更详细地控制它。特别是,您可以在{{1}}数组变量中获取管道的每个组件的状态。