我有几百万个.jpg
文件,我想.jpg.webp
为每个文件生成一个版本 ( foo.jpg
-> foo.jpg.webp
)。为此,我需要找到所有.jpg
以没有.jpg.webp
版本结尾的文件。
现在,我这样做:
find "$path" -type f -iname "*.jpg" |
while read -r image_path; do
if [ ! -f "$image_path.webp" ]; then
echo "$image_path"
fi
done |
# treat only 10000 files per run
head -n 10000 |
...
但是,因为我使用的是管道,所以这会创建一个子 shell。我想知道是否有更有效的方法来做到这一点,特别是因为我生成的 WebP 图像越多,脚本花费在过滤路径来查找候选者上的时间就越多。是否有某种方法可以find
仅使用来做到这一点?
我使用的是 Ubuntu 20.04。文件分布在子目录中。
答案1
我会做以下事情:
- 查找所有后缀(即
*.jpg.webp
)文件,将它们放入排序列表中删除他们的后缀; - 查找所有不带后缀(即
*.jpg
)的文件,将它们放入第二个排序列表中 - 比较两个列表,删除第一个列表中的条目。
- 在由此产生的“设置差异”列表上操作您的转换。
所以,像
#!/bin/bash
comm -z -1 -3 \
<(find -name '*.jpg.webp' -print0 | sed 's/\.webp\x0/\x0/g' | sort -z) \
<(find -name '*.jpg' -print0 | sort -z) \
| parallel -0 gm convert '{}' '{}.webp'
假设您使用 GraphicsMagickgm
进行转换(根据我的经验,速度和可靠性方面比 ImageMagick 更可取convert
),并假设您安装了 GNU parallel
(如果没有,xargs
可能也可以工作)。
答案2
尝试这样的事情:
find "$path" -type f -iname "*.jpg" -exec \
sh -c 'for f; do [ -e "$f.webp" ] || echo "$f" ; done' find-sh {} +
它会执行sh
尽可能少的次数(取决于 find 找到的 .jpg 文件的数量),受到 ARG_MAX(Linux 上大约 200 万字节)的限制,并while read ...
通过将所有文件名作为命令行参数传递来避免极其缓慢的循环。看为什么使用 shell 循环处理文本被认为是不好的做法?和为什么循环查找的输出是不好的做法?
为了有效地处理这些文件的批次,我会将输出重定向到一个文件,然后将其分成 10,000 个批次(或您需要的任意数量),例如使用split -l 10000
.
注意:如果您的任何 .jpg 文件名包含换行符,那么您需要使用 NUL 作为它们之间的分隔符,否则使用换行符作为分隔符。要使用 NUL 分隔符,请替换echo "$f"
为printf "%s\0" "$f"
.顺便说一句,split
支持 NUL 分隔的输入-t '\0'
。
处理批次的脚本应读取文件名,并.jpg.webp
在运行生成版本所需的任何内容之前再次检查相应的文件是否不存在(如果在生成列表后生成文件).jpg.webp
。
如果必须使用 NUL 作为文件名分隔符,那么最简单的方法是使用readarray
(AKA mapfile
)将整个批次的列表读入数组并迭代文件名数组。或者使用 awk 或 perl 来处理文件名。
实际上,即使使用换行符作为分隔符,使用数组也比 while-read 循环更好。
答案3
这听起来像是一份工作make
。它只会生成丢失的文件,或者修改时间比生成文件的文件早的文件。
.PHONY: all
all: $(addsuffix .webp,$(shell find . -name '*.jpg'))
%.jpg.webp: %.jpg
cwebp $< -o $@ #Some command that generates $@ from $<
将其保存到名为 的文件中Makefile
,然后运行make
。
或者make -j $(nproc)
运行与逻辑核心一样多的并行作业。或者选择一个明确的数字,也许是身体的核心,留下一些空闲的逻辑核心用于其他工作。)
如果任何文件或子目录的名称中包含空格,这将会中断。
%.jpg.webp: %.jpg
是一个模式规则。
答案4
是否有某种方法可以
find
仅使用来做到这一点?
在不考虑性能和时间的情况下,这是find
执行此任务的最简单命令:
find "$path" -type f -iname "*.jpg" ! -exec test -e '{}.webp' \; -print
它可能不会像其他答案那么快,但仅供参考。
顺便说一句,如果您只想查找以小写结尾的文件jpg
,最好使用-name
(区分大小写)而不是-iname
(不区分大小写),这可能会慢一点,特别是对于数百万个文件。