为什么这个单行破坏文件?

use*_*084 3 bash find tr

我写这个是为了帮助我批量编码一些视频:

find -name '*.mkv' -exec HandBrakeCLI -Z "Android Tablet" \
  -c "1-3" -m -O --x264-tune film -E copy --decomb -s 1 -i {} \
  -o `echo {} | tr mkv m4v` \;
Run Code Online (Sandbox Code Playgroud)

然而,它失败了——它需要每个 700-900MB 的文件并用 36KB 的文件替换它。

值得注意的是,HandBrake 能够读取和打印视频文件的属性;只有当它继续对其进行编码时,它才会因为文件无效而失败。所以看起来覆盖发生在它到达......

-o `echo {} | tr mkv m4v`
Run Code Online (Sandbox Code Playgroud)

...但我不确定为什么应该这样做。我只是想将输出文件名从whatever.mkv 更改为whatever.m4v

文件名没有空格。例如,它们的形式12-the-revenge.mkv为 。

ric*_*ici 8

反引号表达式:(echo {} | tr mkv m4v不是您想要的,出于各种原因;见下文)在解析 find 命令时展开一次。如果您使用的是bash,它通常会扩展为{}

$ echo {} | tr mkv m4v
{}
Run Code Online (Sandbox Code Playgroud)

而且,事实上,这发生在我机器上安装的每个 shell 上,除了 fish,它输出一个空行。假设您没有使用fish,那么find实际看到的参数将是:

find -name '*.mkv' -exec HandBrakeCLI -Z "Android Tablet" \
-c "1-3" -m -O --x264-tune film -E copy --decomb -s 1 -i {} \
-o {} \;
Run Code Online (Sandbox Code Playgroud)

简而言之,HandBrakeCLI为输入和输出提供了相同的文件,我认为这就是您所看到的,尽管您对症状的描述不是很准确。

不幸的是,没有简单的方法可以找到在操作中执行您希望它执行的扩展名替换-exec。您可以通过将命令传递给 来实现bash -c,但这将涉及额外的、令人困惑的命令行引用。一个更清晰、更易读的解决方案是创建一个 shell 脚本文件,它可以遍历它的参数:

文件:(mkvtom4v.sh确保你chmod a+x mkvtom4v.sh

#!/bin/bash
for file in "$@"; do
  HandBrakeCLI -Z "Android Tablet" -c "1-3" -m -O \
               --x264-tune film -E copy --decomb -s 1 \
               -i "$file" -o "${file/%.mkv/.m4v}"
done
Run Code Online (Sandbox Code Playgroud)

然后用 find 调用它:

find /path/to/directory -name '*.mkv' -exec /path/to/mkvtom4v.sh {} +
Run Code Online (Sandbox Code Playgroud)

一些注意事项:

  1. 不要使用反引号 ( some command); 它们已被弃用多年。使用$(some command),它更具可读性并允许嵌套。

  2. tr绝对不是你想要的。它逐字翻译。例如,tr aeiou AEIOU将使所有元音变为 UppErcAsE。tr mkv m4v将每个更改k为 a 4。有关更多详细信息,请参阅man tr(或info tr)。

  3. "${file/%.mkv/.m4v}"是 bash 特有的搜索和替换语法。在这种情况下,它的意思是:“取 , 的值$file,如果它以.mkv%在此上下文中表示“以”结尾),则将其替换为.m4v“。还有很多其他的 bashisms 用于编辑变量的值。man bash并搜索“参数扩展”。

  4. 使用 GNU find,您可以{} +-exec命令的末尾使用(而不是\;)。它将被尽可能多的找到的文件名替换。有关info find更多详细信息,请参阅。