我有下面的 bash 脚本,是我用我非常差的 bash 知识创建的,用于将 JPG/PNG 文件批量转换为 JPEG XL,到目前为止,该脚本可以很好地满足我的需求,没有任何问题。
我唯一无法解决的问题是优化负责检查图像是否具有与“视觉无损”JPEG XL 不兼容的 ICC 配置文件的循环部分。
我最初的想法是将 Find 和 Parallel 与 IF 和 ELSE 结合在一起,但没有取得任何成功,只导致大量语法错误输出,因此作为替代方法,我选择使用循环,但在具有多个文件的文件夹中检查过程很慢,有时比转换本身花费的时间更长,所以我问,如何优化脚本的这一部分?
#!/bin/bash
# create a copy of all folders and subfolder inside a path called jxl #
find . -type d -not -path "./jxl/*" -exec mkdir -p ./jxl/{} \; -exec mkdir -p ./jxl/icc/{} \;
rmdir ./jxl/jxl
rmdir ./jxl/icc/jxl
# move images with a NOT compatible icc profile to a directory called icc inside jxl path #
dir="./jxl/icc"
icc1="Device Model : "
icc2="Device Model : NONE"
icc3="Device Model : MS30"
shopt -s globstar
for f in **/*.jpg **/*.jpeg **/*.jpe **/*.png
do
check=$(exiftool -devicemodel "$f")
if [ "$check" = "$icc1" ] || [ "$check" = "$icc2" ] || [ "$check" = "$icc3" ]; then
echo "$f = icc profile NOT compatible"
mv "$f" "$dir/$f"
else
echo "$f = icc profile compatible"
fi
done
# Run cjxl encoder e ignore all files inside the jxl folder
find ./ -type f \( -iname \*.jpg -o -iname \*.jpeg -o -iname \*.jpe -o -iname \*.png \) -not -path "./jxl/*" -print0 | parallel --jobs 8 -0 cjxl '{}' './jxl/{.}.jxl' -d 1 -e 7 -E 3 -I 1 --lossless_jpeg 0\;
# copy all files that are not a image to the jxl folder
find ./ -type f \( -iname \*.* ! -iname \*.jpg ! -iname \*.jpeg ! -iname \*.jpe ! -iname \*.png ! -iname \*.sh ! -iname \*.html \) -not -path "./jxl/*" -print0 | parallel --jobs 5 -0 mv '{}' './jxl/{}' \;
#delete all empty folders inside the jxl folder
find ./jxl -type d -empty -delete
答案1
我能看到的唯一真正明显的改进是避免多次分叉外部程序。这是否值得“修复”的麻烦取决于每个命令被分叉了多少次find
或bash
……如果它只是少数或数十个文件,则可能不会。如果是数百或数千,那么肯定是的。
例如,在第一个find
,你正在跑步mkdir
两次对于找到的每个文件。您可以通过将其编写为来优化它:
find . -type d -not -path "./jxl/*" \
-exec bash -c 'for d; do
printf './jxl/%s\0./jxl/icc/%s\0' "$d" "$d";
done | xargs -0r mkdir -p ' bash {} +
这用于printf
发送 NUL 分隔的目录列表以使用xargs -0r mkdir -p
.
稍后在脚本中的for f in **/*.jpg **/*.jpeg **/*.jpe **/*.png
循环中,您将为mv
每个非 ICC 兼容文件执行一次。您可以通过构建一个包含要移动的文件的数组来优化它,并再次使用printf
将 NUL 分隔的列表发送到xargs -0r mv -t "$dir/"
.
例如:
declare -a mvfiles=()
for f in **/*.jpg **/*.jpeg **/*.jpe **/*.png
do
check=$(exiftool -devicemodel "$f")
if [ "$check" = "$icc1" ] || [ "$check" = "$icc2" ] || [ "$check" = "$icc3" ]; then
echo "$f = icc profile NOT compatible"
mvfiles+=("$f")
else
echo "$f = icc profile compatible"
fi
done
printf '%s\0' "${mvfiles[@]}" | xargs -0r mv -t "$dir/"
请注意,这假设您使用的是mv
带有-t
允许您指定目标目录的选项的 GNU前所有源路径名。不值得对其他版本执行此操作,mv
因为您必须使用xargs
'-I
选项,并且这会为每个文件名运行一个选项mv
,从而违背了这样做的目的。当然,除非您使用 FreeBSD 版本的xargs
with-J
选项(其工作方式类似于-I
但允许每个命令使用多个参数)。
您可以不使用xargs
,只使用 ,mv "${mvfiles[@]}" "$dir/"
但是如果您有数千个文件,那么您将面临超过 ARG_MAX 的风险。使用 xargs 可以避免任何风险。
顺便说一句,我不确定如何parallel
处理{}
(我主要用于xargs -P
并行任务),但您可能想研究一下。考虑到您要同时parallel
运行 5 个进程,它可能会为每个参数运行一个命令。mv
这可能正是您运行时所需要的cjxl
(我没有安装它,也不知道它如何处理其文件名参数,所以我无法发表评论),但对于mv
您来说,最好mv -t
通过管道进入来使用xargs -0r mv -t ./jxl/
另外值得注意的是:使用parallel
一次运行多个进程可能无法提供您期望的性能提升。这取决于进程是否受到 I/O 或可用 CPU 功率的限制。
如果他们是不是如果 I/O 带宽匮乏,那么并行运行它们将会带来巨大的性能提升。
如果进程 I/O 匮乏,那么它们在等待输入时大部分时间都会处于空闲状态,特别是当它们最终相互竞争 IO 时。所以,通过实验来找出最优数cjxl
在不超过硬件可用 IO 带宽的情况下可以运行的作业数量 - 8可能是正确的数字,或者可能更少。如果你有超过 8 个核心,它甚至可能会更多,但这不太可能。
答案2
我强烈建议您更改脚本以包含从 GNU Parallel 调用的单个 bash 函数:
doit() {
# Do all processing of a single file here
# including if statements
}
export -f doit
find ... | parallel doit
这样做的好处是您可以一次在一个文件上测试您的函数。
另一个好处是您可以混合 CPU 和 I/O 密集型部件。因此,如果幸运的话,一项工作将使用您的 CPU,而另一项工作将使用您的磁盘。