递归重命名文件名中的撇号 ' (Bash)

递归重命名文件名中的撇号 ' (Bash)

需要从文件中删除撇号。我尝试了几种方法,也来自堆栈交换

我使用的是 Synology NAS,所以我没有 Python 或 Perl,而且我必须排除某些目录 (find -prune)。

以下 find 命令似乎在测试期间通常有效(使用 echo):

find . -depth \( -type d -name "@*" -o -name "*#recycle*" -o -name "*SynoResource*" -prune \) -o \( -name "*\'*" -execdir bash -c 'for f; do echo mv ${f/\\\'/\\\\'} `echo $f | sed 's/\\\'/X/g'`; done' _ {} + \)

对于像“911's.zip”这样的文件,将撇号 ' 替换为 X 会生成以下结果(使用上面的命令):

mv ./911's.zip ./911Xs.zip

当我删除“echo”以执行命令时,出现以下错误:

mv: target '911Xs.zip' is not a directory

在交互式 shell 中,我可以使用以下命令重命名该文件:

mv 911\'s.zip 911Xs.zip

当然,我尝试转义 ' (即使有几个 \,如${f/\'/X}),但它不起作用。

任何想法?

多谢,

加里

答案1

几个问题:

  • 您无法将-prune与结合起来,-depth因为-depth进程首先离开,因此-prune在处理目录的全部内容之后,它来得太晚了,因此没有效果。
  • 你的-prune放错地方了。你把它放在哪里,它只会适用于匹配的文件-name "*SynoResource*"。相同,-type d仅适用于-name "@*".
  • 您的参数扩展和命令替换缺少双引号,这会导致它们进行 split+glob。
  • echo一般不能用于输出任意数据。
  • 不能有'内部单引号字符串。如果需要将'字面意思传递给某个命令,则需要使用另一个 shell 引用运算符("..."或反斜杠)并在单引号之外引用它。
  • '对他们来说并不特殊mvsedfind没有必要逃避它。它仅对 shell 来说是特殊的,它是一个强引用运算符。
  • 您应该使用$(...)而不是不推荐使用的`...`(当涉及反斜杠时,这也会增加更多的复杂性)。

所以在这里:

LC_ALL=C Q="'" find . -depth \
  ! -path '*/@*' \
  ! -path '*#recycle*' \
  ! -path '*SynoResource*' \
  -name "*'*" -execdir bash -c '
    for file do
      mv -i -- "$file" "${file//$Q/X}"
    done' bash {} +

在这里,由于-prune不能与 一起使用-depth,我们使用-path-wholename在 GNU 中也称为find)根据完整路径过滤掉这些排除目录中的文件。然而,这意味着它将find进入那些从性能角度来看并不理想的目录。

使用-execdir意味着您只重命名基本名称,这很好,但这也意味着您最终会在bash每个包含带有's 的文件的目录中运行至少一个。或者,你可以这样做:

LC_ALL=C Q="'" find . -depth \
  ! -path '*/@*' \
  ! -path '*#recycle*' \
  ! -path '*SynoResource*' \
  -name "*'*" -exec bash -c '
    for file do
      dir=${file%/*} base=${file##*/}
      mv -i -- "$file" "$dir/${base//$Q/X}"
    done' bash {} +

这使得它更高效,但在脚本运行时面对有人重命名文件和目录时不太安全。

对于空运行,您可以将该mv命令替换为:

(PS4="Would run"; set -x; : mv -- "$file" "$dir/${base//$Q/X}")

echo这比使用as更好,因为bash会在跟踪输出中必要时使用引号,以使其明确。跟踪输出看起来就像是可用于运行相同命令的有效 shell 代码。

在那里,使用-exec代替-execdir也可以清楚地跟踪哪些文件将被重命名。

要使用但仍然首先处理目录深度,另一种方法是在输出上-prune使用 GNU tac -s ''(如果可用)(以及 GNU )来反转它:xargsfind -print0

LC_ALL=C find . -depth \
  '(' -name '@*' -o \
      -name '*#recycle*' -o \
      -name '*SynoResource*' \
  ')' -type d -prune -o \
  -name "*'*" -print0 |
  tac -s '' |
  Q="'" xargs -r0 bash -c '
    for file do
      dir=${file%/*} base=${file##*/}
      mv -i -- "$file" "$dir/${base//$Q/X}"
    done' bash {} +

没有 GNU tacxargs但如果你的 bash 是 4.4 或更高版本,你也可以这样做:

LC_ALL=C find . -depth \
  '(' -name '@*' -o \
      -name '*#recycle*' -o \
      -name '*SynoResource*' \
  ')' -type d -prune -o \
  -name "*'*" -print0 |
  Q="'" bash -c '
    readarray -td "" files &&
      for (( i = ${#files[@]} - 1; i >= 0; i-- )); do
        file=${files[i]}
        dir=${file%/*} base=${file##*/}
        mv -i -- "$file" "$dir/${base//$Q/X}"
      done' bash {} +

(请注意,我还没有测试过这些)。

相关内容