给目录下的所有目录添加下划线字符前缀

给目录下的所有目录添加下划线字符前缀

我需要重命名目录中的多个目录,以在名称开头包含下划线“_”字符。

作为测试,我创建了一个父目录,其内容包含三个目录:

dir1, dir2 and dir3; three files: file1, file2, file3

和三个点前缀文件:

.file1, .file2, .file3 

到目前为止,我已经尝试在 Mac 上gfind安装后使用 , :findutils

gfind . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh

这将返回:

mv: rename ./. to ./_.: Invalid argument

所以,我尝试使用重命名和查找:

find . -type d -exec rename 's/^/_/' {} \;

这将返回:

Can't rename '.' to '_.': Invalid argument
Can't rename './dir1' to '_./dir1': No such file or directory
Can't rename './dir2' to '_./dir2': No such file or directory
Can't rename './dir3' to '_./dir3': No such file or directory

有人碰巧有一个可行的解决方案或者知道我可能会在哪里犯错吗?

答案1

如果我没猜错的话,你会有这样的文件和目录:

$ mkdir dir{1,2,3} ; touch file{1,2,3} .file{1,2,3}
$ ls -A
.file1  .file2  .file3  dir1  dir2  dir3  file1  file2  file3

并想将目录重命名为_dir1, _dir2, _dir3

find/printf mv看起来可行。让我们看看它打印了什么:

$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n"
mv "./." "./_."
mv "./dir2" "./_dir2"
mv "./dir3" "./_dir3"
mv "./dir1" "./_dir1"

第一个命令会抛出错误,因为“点”很特殊,您无法重命名它。但其他的应该没问题,并且应该根据需要重命名目录:

$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh
mv: cannot move ‘./.’ to ‘./_.’: Device or resource busy
$ ls -d _dir*
_dir1  _dir2  _dir3

但是通过管道将命令传送到 shell 有点难看,如果文件名足够奇怪,结果将会令人惊讶(例如,如果它们包含$将在 shell 中触发变量或命令替换的符号)。

如果文件都在一个级别上,那么应该这样做:

for x in * ; do [ -d "$x" ] && mv "$x" "_$x" ; done

(尽管如果_$x已经存在,$x则会被移入其中。)

如果要包含名称以点开头的目录,请shopt -s dotglob提前使用。

这也很接近:

find . -type d -exec rename 's/^/_/' {} \;

但由于find给出了rename以 开头的路径./,我们需要考虑到这一点。仅在一个层面上,这应该可以(将前导更改././_):

find . -type d -exec rename 's,^./,./_,' {} \;

要获取所有级别的目录,find -execdir可能是最容易使用的。它在文件的目录中运行命令。我们需要-depth以正确的顺序处理重命名。

find . -depth -type d -execdir rename 's,^./,./_,' {} \;

或许也可以添加! -name .。例如

$ mkdir foo foo/dir1 foo/dir2
$ find . -depth \! -name . -type d -execdir rename 's,^./,./_,' {} \;
$ ls  _foo
_dir1  _dir2

答案2

您第一次尝试的问题是,.除了所有子目录之外,您还尝试重命名自身(当前目录)。跳过它。

gfind . -mindepth 1 -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh

但实际上,不要这样做。切勿将东西通过管道输入sh.这不起作用除非您的文件名碰巧不包含任何 shell 特殊字符。如果您有一个名为`rm -r ~`(完全有效,但不常见的文件名)的文件,则 pop 会进入您的主目录。调用 shell,-exec如下所示。

您的第二次尝试不起作用,因为您_在完整路径的开头插入 ,而不是在基本名称的开头添加它,即在最后一个/.

find . -mindepth 1 -type d -exec rename 's![^/]+$!_$&!' {} \;

如果您有嵌套目录,这些实际上都不起作用,因为它们在find遍历目录时会重命名目录。当您执行此操作时,您需要先对目录的内容进行操作,然后再使用该-depth选项对目录本身进行操作。

find . -mindepth 1 -depth -type d -exec rename 's![^/]+$!_$&!' {} \;

更方便的是,调用 shell 并运行mv。这适用于任何 POSIX 系统。

find . -depth -type d -name . -o -exec sh -c '
    for d do mv "$d" "${d%/*}/_${d##*/}"; done
' sh {} +

如果没有嵌套子目录,那么您可以使用简单的 shell 循环。如果不想重命名以点开头的目录,只需使用通配符*/,它​​只匹配目录和目录的符号链接。

for d in */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi   # robustness in case there are no subdirectories
  if [ -L "$d" ]; then continue; fi     # optional: skip symbolic links to directories
  mv -- "$d" "_${d%/}"
done

如果要重命名所有目录(包括名称以点开头的目录),请.*同时包含通配符。

for d in .*/ */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi
  if [ -L "$d" ]; then continue; fi
  mv -- "$d" "_${d%/}"
done

请注意,当您移动foo到​​时_foo,如果已经有一个名为 的目录_foo,则会将旧目录移动foo_foo/foo。为了防止这种情况,请添加显式测试。

或者,也可以使用 zsh。它预装在 macOS 上。第一次运行autoload -U zmv(将其放入您的.zshrc或您的脚本中)以加载zmv功能,那么非递归版本(包括点文件,不包括符号链接 - (/D)是一组全局限定符):

zmv -Q '*(/D)' '_$f'

或递归版本(使用修饰语提取目录名和基本名):

zmv -Q '**/*(/D)' '$f:h/_$f:t'

zmv如果目标已经存在,将拒绝采取行动。

相关内容