find
用于{}
指示“此文件”(ish)。您可以将一系列文件输入到 中myprog
,因此:
find ./tests/ -name *.in -exec myprog -i {} \;
有没有办法修改里面的名字{}
?就我而言,我用来-i
定义输入文件和-o
输出,并且我希望输出稍微修改一下文件名,这样“a.in”将生成“a.out”。理想情况下,我想要达到以下效果:
find ./tests/ -name *.in -exec myprog -i {} -o {}.out \;
此外,输出目录可能是不同的路径。在这种情况下,输出可能不会到达,/tests/
但可能会到达/tests_20220615/
。
我已经查看了许多带有 示例的页面find
,但没有出现类似的情况,所以也许“不”?
我知道有一些方法可以在 bash 或 zsh 中使用循环来做到这一点,但是可能的陷阱列表很棒(“nullglob”?!),如果find
可以做到这一点,那么对于这个菜鸟来说似乎更安全。
答案1
find
不会让您修改文件的路径,某些find
实现不会让您{}
与其他内容连接,有些甚至不支持{}
多次传递,但您始终可以运行一些命令,例如可以进行转换的 shell:
find ./tests/ -name '*.in' -type f -exec sh -c '
ret=0
for file do
myprog -i "$file" -o "${file%.*}.out" || ret="$?"
done
exit "$ret"' sh {} +
myprog
在上面,我们不是直接执行,而是执行sh
并向其传递一些内联代码以及找到的文件的路径(而{} +
不是{} ';'
传递尽可能多的文件)。
sh
依次循环这些文件,并myprog
在对它们应用转换后调用,例如${file%.*}
删除扩展名。
请注意 周围的引号*.in
。如果没有它们,运行该命令的 shellfind
会尝试将其扩展为当前目录中名称以 结尾的文件列表,.in
而不是将该模式逐字传递给find
。
上面,我们告诉sh
如果任何myprog
调用失败,则以失败退出状态退出。该失败将反映在 的退出状态中find
,因此您可以根据需要采取操作,或者在errexit
启用该选项的情况下退出脚本。但不可能在第一次失败时中止myprog
。
如果使用zsh
shell,您还可以在内部进行查找:
set -o errexit
for file (./tests/**/*.in(ND.)) myprog -i $file -o $file:r.out
将在第一次失败时退出,并且还将按词法顺序处理列表(您始终可以添加oN
glob 限定符来禁用该排序)。
另一种方法是打印find
文件,将其通过管道传输到执行转换的某个命令,然后通过管道传输到xargs
.例如:
find ./tests/ -name '*.in' -type f -print0 |
gawk -v RS='\0' -v ORS='\0' -v OFS='\0' '
{
filein = fileout = $0; sub(/\.in$/, ".out", fileout)
print "-i", filein, "-o", fileout
}' | xargs -r0 -n4 myprog
如果任何调用失败,将再次xargs
返回非零退出状态。 myprog
GNUxargs
可以与其-P
选项并行运行多个调用。
或者你可以perl
对其进行后处理并让它运行:
find ./tests/ -name '*.in' -type f -print0 |
perl -l -0ne '
system("myprog", "-i", $_, "-o", s/\.in\Z/.out/r) == 0 or
$ret = 1;
END {exit $ret}'
请注意,后处理 的输出的方法find
将掩盖其失败退出状态(如果有)(例如无法进入某些目录时),除非您设置了pipefail
shell(如果支持)。
使用管道还会影响myprog
标准输入的内容(例如,如果需要提示用户)。 GNU在、其他一些xargs
上打开标准输入,该方法将保持原样,这意味着它将是来自/ 的管道。/dev/null
perl
find
gawk
答案2
您已经收到了很好的答案。不过,我觉得你这个问题的前提并不是应该的。您不应该害怕在 bash 中编写循环,并且考虑到您仍然需要小心使用其他实用程序,那么我认为在这种情况下没有任何理由不使用 bash。
在这个例子中,简单地这样做并没有什么问题:
for file in test/*; do
[[ -e "$file" ]] || continue
echo cp "$file" "${file/test/tests_20220615}.out";
done
cp test/1.in test_20220615/1.out
cp test/10.in test_20220615/10.out
cp test/2.in test_20220615/2.out
cp test/3.in test_20220615/3.out
cp test/4.in test_20220615/4.out
cp test/5.in test_20220615/5.out
...
无论默认nullglob
行为如何,这都会起作用。特别是,添加一个条件来检查文件([[ -e "$i" ]]
如果存在具有该名称的文件,则为 true),如果您不确定输出,请在那里抛出一个 echo 语句(甚至更好printf
)并确保一切看起来都正确。