我正在使用 bash 脚本创建 Git 挂钩。我想对我提交的文件运行一个进程(linting)。我只想在文件实际存在时运行该进程(换句话说,不考虑在我的提交中删除的文件)。我试过这个
CHANGED_FILES=$(git diff --cached --name-only | grep ".js$")
echo "$CHANGED_FILES"
if [ -n "$CHANGED_FILES" ]; then
REAL_FILES=()
for pathname in "${CHANGED_FILES[@]}"; do
if [ -e "$pathname" ]; then
REAL_FILES+=( "$pathname" )
fi
done
echo "real files are ..."
echo "$REAL_FILES"
但以上仅在单个文件发生更改时才有效。如果有两个或更多文件发生更改,则不会发生任何事情,我的输出看起来就像
src/app/test1.js
src/app/test2.js
real files are ...
因此,当 CHANGED_FILES 数组包含 2 个或更多实际存在的文件时,不会为“REAL_FILES”保存任何内容。
我该如何解决上述问题?
答案1
您的脚本的问题在于 CHANGED_FILES 是标量变量,而不是数组。
尝试:
CHANGED_FILES=( $(git diff --cached --name-only | grep ".js$") )
即在命令替换周围添加括号$(git...)
。
请注意,这受通常的 shell 单词分割的影响,因此如果有任何机会 (*) 在任何路径名中可能存在空格/换行符/shell 元字符,您应该使用mapfile
和流程替代代替命令替换,可以选择同时使用和 的-z
NUL 分隔符。例如:git diff
grep
mapfile -d '' -t CHANGED_FILES < \
<(git diff -z --cached --name-only | grep -z '\.js$')
顺便说一句,mapfile
和readarray
在 bash 中是同义词。请参阅help mapfile
了解详情。在 中ksh
,readarray
只有 和mapfile
不存在。
(*) 有总是有机会这样做,因为这些字符是 UNIX 文件系统上的有效文件名字符。路径名中唯一无效的字符是 NUL(路径名的文件名部分中唯一无效的字符是/
和 NUL)。因此,您应该始终以防御性方式编写脚本,并假设某处总会至少存在一个这样令人讨厌的文件名。即始终使用该mapfile
版本。
顺便说一句,如果你想打印 $REAL_FILES 数组的所有元素(而不仅仅是第一个元素),那么使用[@]
下标或使用declare -p
/ typeset -p
(这更适合调试。并生成可以在 bash 中执行而无需引用 / 的输出)空白问题)。
例如,而不是这个:
echo "$REAL_FILES"
做这个:
echo "${REAL_FILES[@]}"
或这个:
typeset -p REAL_FILES
(declare
vstypeset
是另一个bash
vsksh
差异。ksh 有readarray
和typeset
,然后 bash 从 ksh 复制它们并将它们重命名为mapfile
和declare
并将 ksh 版本添加为它们的别名。我不知道为什么)
答案2
您不需要echo "$REAL_FILES"
将数组连接成一个字符串:
echo "${REAL_FILES[*]}"
或者您可以执行每个元素的打印输出:
printf "%s\n" "${REAL_FILES[@]}"