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


9

我有一些带有\n字符名称的文件夹。

例如:

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

多数民众赞成指的是一个文件夹,其名称为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只是一个文件夹?


3
您需要使用find的-print0指令和-dread选项。参见stackoverflow.com/a/40189667/7552
格伦·杰克曼(Glenn Jackman)

1
@glennjackman请回答!
甜点

@glennjackman感谢您的答复,但find * -type d -print0 | while IFS= read -d '' file ; do rmdir $file ; done命令有此输出 rmdir: failed to remove 'Test': No such file or directory
塔拉S Volpe

5
必须引用变量:rmdir "$file"
glenn jackman

1
@glennjackman只需将其发布为答案。这是一个适当的解决方案,除了评论之外,这不是最好的选择。已暗示Upvote :)
Sergiy Kolodyazhnyy

Answers:


12

您在那里只有一个命令,因此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引号 $'...'-nameopption 结合使用:

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

POSIX,我们可以这样处理:

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

值得一提的是,如果您的目标是删除目录,则已-delete足够,但是,如果要在目录上执行命令,则-exec最合适。

也可以看看


2
…… rm -rf 小心使用。; P不find具有一个-delete选项BTW?它会删除空目录,并为非空目录(例如rmdir,可能会更便宜)抛出错误。
甜点

3
@dessert可以,我在其中添加了该内容,但-delete不会删除非空目录。因此,请谨慎使用rm -rf
Sergiy Kolodyazhnyy

6
总是find没有任何破坏性有效载荷的情况下空运行此类命令(例如,删除-delete-exec whatever \;),以检查受影响文件的列表是否确实正确,并且不包含不应删除的内容...
字节指挥官

2
这是askubuntu.com,因此我们可以假定GNU find。用于-exec rmdir {} +将多个arg批处理到一个rmdir命令行上。顺便说一句,“ 但它不适用于非空目录。 ”同样适用rmdir,因此实际上与没什么区别-delete。与有所不同rm -r
彼得·科德斯

2
find -type d -empty -delete-delete表示-depth)。
凯文(Kevin)

10

您可以使用shell glob代替find

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

Shell Glob */与当前目录中的所有文件夹匹配。这种for循环构造可自动进行正确的单词拆分。

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

同样不要忘记总是引用您的变量。


水珠只能找到文件/目录在当前目录中,而不是像递归find
ilkkachu

1
你说得对@ilkkachu。可以通过启用globstar shell选项shopt -s globstar并改用**/*/glob来更改。
字节指挥官

6

到目前为止,这两个答案都对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在此答案中解释了第三个和IMO绝妙的方法:

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

这使用内置的shell printf来构建零定界的参数列表,然后将此列表通过管道传递给xargs其,rmdir以根据需要进行多次调用。您可以使用shopt -s globstar**/*/而不是*/如上所述来使其递归工作。


2
find是递归的,但OP的循环不是。要复制该行为,请使用find -maxdepth 1 -type d -exec rmdir {} +。(-depth在这种情况下是多余的。)printf要模仿的巧妙方法find -print0,我以前从未见过。
彼得·科德斯

1
哦! 我看到了lsand while循环,我想念他们是从流程替代中重定向而来的,find而不是通过管道ls将其重定向到循环中,并且由于某种原因而没有显示它。那find *真是埋葬了。是的,它是递归的,并且如果目录中的目录条目太多(包括非目录)会失败,因为它不使用*/find .会更好,因为ing比bash的全局扩展find更快stat
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.