这是一个更大的bash(Fedora 30)脚本的一部分示例,该脚本显示了我遇到的问题
该脚本应该遍历目录树并在发现任何文件名超过103个字符时退出。
SS_NORMAL=0
JOLIET_MAX=103
MD5FILE=/tmp/blah.md5
function myExit
{
echo Exiting $1 ...
exit
}
function traverse
{
find . -type f -print0 |
while IFS= read -r -d '' MD5_FILESPEC; do
MD5_BASE=$(basename "$MD5_FILESPEC")
if [ "${#MD5_BASE}" -gt "$JOLIET_MAX" ]; then
myExit "[FAIL] Filename size ${#MD5_BASE} too long - $MD5_FILESPEC"
fi
done
}
traverse
date; date; date
它可以正常工作,直到找到那些长文件名之一。它调用myExit并退出循环,但并非一直退出脚本。我总是在输出的末尾看到这三个日期,我应该不会。
我该如何处理?
答案 0 :(得分:3)
解决while
循环在子进程中运行的问题的一种简单方法是进行逆转:让while
在同一shell中运行,这是一个子进程来生成文件
但是,如果来自find
的文件名的接收者必须位于|
运算符的右边,我们该怎么做?答案是,在GNU Bash中,我们有一个语言扩展名为“进程替换”。
进程替换是Bash转换为看起来像文件名的字符串的一种语法,并被命令以这种方式接受。程序打开并读取该文件(或从另一个方向写入文件)时,它将通过管道与另一个进程进行通信。
想法的构想:
while IFS= read -r -d '' MD5_FILESPEC; do
MD5_BASE=$(basename "$MD5_FILESPEC")
if [ "${#MD5_BASE}" -gt "$JOLIET_MAX" ]; then
myExit "[FAIL] Filename size ${#MD5_BASE} too long - $MD5_FILESPEC"
fi
done < <(find . -type f -print0)
# ^^^^^^^^^^^^^^^^^^^^^^^^^ this is the process substitution
答案 1 :(得分:1)
如@KamilCuk所述,循环中从find
到read
的管道意味着exit
出现在子外壳中。也就是说,当您拥有find . -type f -print0 | <do some stuff>
,并且<do some stuff>
包含对exit
的调用时,exit
适用于|
创建的子外壳中。对于您的脚本,myExit
正在调用exit
,它结束了管道右侧的while
循环,但这并未结束{{1 }}实际上正在运行。 traverse
然后结束,traverse
被执行3次,脚本结束。
最简单的选择可能是在脚本顶部使用date
。这是一种扭曲的方法:如果任何返回非零值,脚本将退出。
为此,将整个set -e
函数替换为:
myExit
现在,从循环内部调用该函数时,您将获得一个非零的返回码,并且脚本应立即退出。
一种更宽容的方法是如上所述更改set -e
function myExit() {
echo "Exiting $1 ..."
return 1
}
,但不添加myExit1
,并在对{{1 }}:
set -e
然后将对return 1
的呼叫更改为此:
myExit
现在,当if [ "${#MD5_BASE}" -gt "$JOLIET_MAX" ]; then
myExit "[FAIL] Filename size ${#MD5_BASE} too long - $MD5_FILESPEC"
return 1
fi
块触发时,它将调用traverse
,后者调用if ! traverse; then exit; fi
,终止子外壳,此时if
返回值{ {1}},条件myExit
的计算结果为exit
,并调用traverse
。
您的问题的另一个答案建议使用流程替换,这也是一个很好的解决方案。这显示了用于这种类型的循环操作的更典型的模式:
1
最后,关于样式偏好,我可能会放弃if
作为一个单独的函数,除非您有计划对其进行扩展。照原样,似乎只是造成了不必要的复杂性。