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

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

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

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

./dir:
subdir.with.dots

./dir/subdir.with.dots:
other.file

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

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

find我尝试用和替换点tr

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

但如果文件路径和文件本身都包含点,则会出现错误

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

答案1

zsh

autoload zmv
zmv '(**/)(*.*)' '$1${2//./_}'

否则,如果您有权访问bash(尽管ksh93zsh也将这样做),您始终可以这样做:

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

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

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

重点是按深度优先处理列表(zmv默认情况下这样做,并使用-depthin find)并且仅重命名文件的基本名称。

这样你就可以:

mv -- a.b/c.d a.b/c_d

然后:

mv -- a.b a_b

另请注意,对于非 zmv 解决方案,如果您在同一目录下有一个a.b文件和一个目录,那么会很高兴地移动a_bmv -- a.b a_ba.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

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

你需要:

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

相关内容