你可以在“查找”过程中修改文件名吗

你可以在“查找”过程中修改文件名吗

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

如果使用zshshell,您还可以在内部进行查找:

set -o errexit
for file (./tests/**/*.in(ND.)) myprog -i $file -o $file:r.out

将在第一次失败时退出,并且还将按词法顺序处理列表(您始终可以添加oNglob 限定符来禁用该排序)。

另一种方法是打印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返回非零退出状态。 myprogGNUxargs可以与其-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将掩盖其失败退出状态(如果有)(例如无法进入某些目录时),除非您设置了pipefailshell(如果支持)。

使用管道还会影响myprog标准输入的内容(例如,如果需要提示用户)。 GNU在、其他一些xargs上打开标准输入,该方法将保持原样,这意味着它将是来自/ 的管道。/dev/nullperlfindgawk

答案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)并确保一切看起来都正确。

相关内容