在名称中带有 \n 字符的文件夹中进行 for 循环

在名称中带有 \n 字符的文件夹中进行 for 循环

我有一些以\n字符命名的文件夹。

例如:

$ ls
''$'\n''Test'

那是指一个带有测试名称且名称前有一个空行的文件夹。

因此,当我在其父目录中运行一些类似的脚本时:

while IFS= read -r d; do 
    rmdir $d
done < <(find * -type d)

表明:

rmdir: failed to remove '': No such file or directory
rmdir: failed to remove 'Test': No such file or directory

因为它运行两次,一次在\n,另一次在Test,因为文件夹名称有两行。

那么我该如何解决这个问题,使得脚本知道\nTest只是一个文件夹?

答案1

你那里只有一个命令,所以find-exec标志调用就足够了rmdir

find -depth -type d -exec rmdir {} \;

或者使用-delete中的选项 find -type d -delete,但它不适用于非空目录。为此,您还需要-empty标志。还请注意,-delete暗示-depth可以跳过。因此,另一个可行的替代方案是将所有内容保留为一个过程:

find -type d -empty -delete

如果目录不为空,则使用rm -rf {} \;。为了仅隔离\n文件名中包含的目录,我们可以结合 bash 的ANSI-C 引用 $'...'-name选项:

find  -type d -name $'*\n*' -empty -delete

POSIX-ly,我们可以这样处理:

find -depth  -type d -name "$(printf '*\n*' )" -exec rmdir {} \;

值得一提的是,如果您的目标是删除目录,那么-delete就足够了,但是如果您想在目录上执行命令,那么-exec是最合适的。

也可以看看

答案2

您可以使用 shell globs 代替find

for d in */ ; do 
    rmdir "$d"
done

shell glob*/匹配当前目录中的所有文件夹。此 for 循环构造会自动处理正确的分词。

请注意,根据您的 shell 选项,这可能会忽略隐藏文件夹(名称以 . 开头)。可以使用命令 更改该行为以匹配当前会话的所有文件shopt -s dotglob

也不要忘记始终引用你的变量。

答案3

到目前为止写的两个答案都是rmdir每个目录调用一次,但是由于rmdir可以接受多个参数,我想知道:有没有更有效的方法?

你可以简单地做

rmdir */

这绝对是最简单、最有效的方式,但在以下情况下可能会抛出错误许多目录(参见gnome-terminal 中命令行参数的最大长度是多少?)。如果您希望此方法以递归方式工作,请启用globstarshell 选项shopt -s globstar并使用**/*/而不是*/

使用 GNU find(如果我们不只是想使用-delete),我们可以这样做

find -depth -type d -exec rmdir {} +

xargs它以“与构建命令行相同的方式”构建命令行( man find)。如果您不想让它以递归方式工作,-depth请替换 。-maxdepth 1

第三个,也是我认为最精彩的方法,由steeldriver在这个答案

printf '%s\0' */ | xargs -0 rmdir

这使用 shell 内置命令printf来构建一个零分隔的参数列表,然后按需要将该列表通过管道传输到xargs哪个调用。您可以使用and而不是像上面那样使其递归工作。rmdirshopt -s globstar**/*/*/

相关内容