用下划线替换文件名中的所有点

rub*_*o77 5 filenames rename

我有一个目录,其中包含一些包含文件名和目录名的子目录,其中包含一些点:

$ ls -R
.:
dir  file.with.dots

./dir:
subdir.with.dots

./dir/subdir.with.dots:
other.file
Run Code Online (Sandbox Code Playgroud)

我真的很难捕获所有这些文件,find因为我不想重命名当前文件夹.及其..本身。

我还阅读了如何在文件名中用下划线替换点,保持扩展名不变,但这并不适合这里,因为主要问题是文件的递归选择。

我试图用find和替换点tr

find $(pwd) -depth -name '*.*'|while read f; do mv -iv "$f" '"'$(echo $f|tr '.' '_')'"' ; done
Run Code Online (Sandbox Code Playgroud)

但是如果文件的路径和文件本身都包含点,这会产生错误

如何用下划线重命名和替换所有出现的点?

Sté*_*las 10

zsh

autoload zmv
zmv '(**/)(*.*)' '$1${2//./_}'
Run Code Online (Sandbox Code Playgroud)

否则,如果您有权访问bash(尽管ksh93zsh也将这样做),您可以随时执行以下操作:

find . -depth ! -name '.*' -name '*.*' -exec bash -c '
  for file do
    dir=${file%/*}
    base=${file##*/}
    mv -i -- "$file" "$dir/${base//./_}"
  done' sh {} +
Run Code Online (Sandbox Code Playgroud)

(尽管您会错过由 完成的健全性检查zmv)。

那些(故意)不会重命名隐藏文件。

关键是处理列表深度优先(zmv默认情况下,并使用-depthin find)并且只重命名文件的基本名称。

这样你就可以:

mv -- a.b/c.d a.b/c_d
Run Code Online (Sandbox Code Playgroud)

随后

mv -- a.b a_b
Run Code Online (Sandbox Code Playgroud)

还要注意,对于非zmv的解决方案,如果你有一个a.b文件和一个a_b目录在同一个目录下,那么mv -- a.b a_b会很高兴地移动a.b a_b/. 为避免这种情况,如果在 GNU 系统上,您可以将该-T选项添加到mv.


正如我所看到的,您试图编辑答案以使用while read循环。

请注意,您通常应该避免while read循环,即使在这种情况下,无论如何必须每行运行一个命令。如果你真的想使用一个,那么正确地使用它是很棘手的,并且涉及到 ksh/bash/zshisms:

{
  find . -depth ! -name '.*' -name '*.*' -exec printf '%s\0' {} + |
    while IFS= read -rd '' file; do
      dir=${file%/*}
      base=${file##*/}
      mv -i -- "$file" "$dir/${base//./_}"
    done <&3 3<&-
} 3<&0
Run Code Online (Sandbox Code Playgroud)

(如果您的发现支持,您可以替换-exec printf '%s\0' {} +-print0)。

你需要:

  • -print0/-exec printf '%s\0' {} +因为您不能依赖换行符作为分隔符,因为换行符是文件名中的有效字符
  • 因此,您需要-d ''read读取直到 NUL 而不是 NL)
  • IFS=(即从IFSfor 中删除所有空白字符,read否则会将它们从输入记录的末尾剥离)。
  • -ras 否则read将反斜杠视为转义字符(用于字段和记录分隔符)。
  • 与文件描述符一起跳舞是因为您希望 的标准输入mv是终端(用于-i提示的答案),而不是来自find.