为什么找不到。-delete删除当前目录?


22

我希望

find . -delete

删除当前目录,但不会删除。为什么不?


3
最有可能的原因是删除当前工作目录不是一个好主意。
Alexej Magura

同意-我喜欢默认行为,但与不一致find . -print
mbroshi

@AlexejMagura尽管我很同情,但我看不出为什么删除当前目录与删除打开的文件应该有什么不同。该对象将保持活动状态,直到存在对其的引用,然后再进行垃圾回收。您可以cd ..; rm -r dir使用语义非常清晰的另一个外壳来做……
Rmano

@Rmano这是真的:原则上我不会做这件事:只需上一个目录,然后删除当前目录。我不确定为什么会有这么大的麻烦-尽管我对当前目录不再存在一些不幸,例如相对路径不再起作用,但是您始终可以使用绝对路径退出-但是我有些人只是说这不是一个好主意。
Alexej Magura

Answers:


29

findutils 意识到它的成员,它与* BSD兼容:

我们跳过删除“。”的原因之一。是为了与* BSD兼容,此操作起源于此。

findutils源代码中的NEWS表明他们决定保留该行为:

#20802: If -delete fails, find's exit status will now be non-zero. However, find still skips trying to delete ".".

[更新]

既然这个问题成为热门话题之一,所以我将深入研究FreeBSD源代码并提出一个更有说服力的理由。

让我们看一下FreeBSDfind实用程序源代码

int
f_delete(PLAN *plan __unused, FTSENT *entry)
{
    /* ignore these from fts */
    if (strcmp(entry->fts_accpath, ".") == 0 ||
        strcmp(entry->fts_accpath, "..") == 0)
        return 1;
...
    /* rmdir directories, unlink everything else */
    if (S_ISDIR(entry->fts_statp->st_mode)) {
        if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
            warn("-delete: rmdir(%s)", entry->fts_path);
    } else {
        if (unlink(entry->fts_accpath) < 0)
            warn("-delete: unlink(%s)", entry->fts_path);
    }
...

如您所见,如果它没有过滤出点和点,那么它将到达rmdir()POSIX定义的C函数unistd.h

做一个简单的测试,带有dirt / dot-dot参数的rmdir将返回-1:

printf("%d\n", rmdir(".."));

让我们看一下POSIX如何描述rmdir

如果path参数引用的路径的最终成分为点或点-点,则rmdir()将失败。

没有给出原因shall fail

我发现rename 解释了一些原因

禁止重命名点或点,以防止循环文件系统路径。

循环文件系统路径

我查看了C编程语言(第二版)并搜索目录主题,令人惊讶的是我发现代码是相似的

if(strcmp(dp->name,".") == 0 || strcmp(dp->name,"..") == 0)
    continue;

和评论!

每个目录始终包含自身的条目,称为“。”,及其父条目“ ..”;必须跳过这些,否则程序将永远循环

“永远循环”,就像上面rename将其描述为“循环文件系统路径”一样

我根据以下答案稍稍修改了代码并使它在Kali Linux中运行:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <dirent.h>
#include <unistd.h>

void fsize(char *);
void dirwalk(char *, void (*fcn)(char *));

int
main(int argc, char **argv) {
    if (argc == 1)
        fsize(".");
    else
        while (--argc > 0) {
            printf("start\n");
            fsize(*++argv);
        }
    return 0;
}

void fsize(char *name) {
    struct stat stbuf;
    if (stat(name, &stbuf) == -1 )  {
        fprintf(stderr, "fsize: can't access %s\n", name);
        return;
    }
    if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
        dirwalk(name, fsize);
    printf("%81d %s\n", stbuf.st_size, name);
}

#define MAX_PATH 1024
void dirwalk(char *dir, void (*fcn)(char *))
{
    char name[MAX_PATH];
    struct dirent *dp;

    DIR *dfd;

    if ((dfd = opendir(dir)) == NULL) {
            fprintf(stderr, "dirwalk: can't open %s\n", dir);
            return;
    }

    while ((dp = readdir(dfd)) != NULL) {
            sleep(1);
            printf("d_name: S%sG\n", dp->d_name);
            if (strcmp(dp->d_name, ".") == 0
                            || strcmp(dp->d_name, "..") == 0) {
                    printf("hole dot\n");
                    continue;
                    }
            if (strlen(dir)+strlen(dp->d_name)+2 > sizeof(name)) {
                    printf("mocha\n");
                    fprintf(stderr, "dirwalk: name %s/%s too long\n",
                                    dir, dp->d_name);
                    }
            else {
                    printf("ice\n");
                    (*fcn)(dp->d_name);
            }
    }
    closedir(dfd);
}

让我们来看看:

xb@dnxb:/test/dot$ ls -la
total 8
drwxr-xr-x 2 xiaobai xiaobai 4096 Nov 20 04:14 .
drwxr-xr-x 3 xiaobai xiaobai 4096 Nov 20 04:14 ..
xb@dnxb:/test/dot$ 
xb@dnxb:/test/dot$ cc /tmp/kr/fsize.c -o /tmp/kr/a.out 
xb@dnxb:/test/dot$ /tmp/kr/a.out .                     
start
d_name: S..G
hole dot
d_name: S.G
hole dot
                                                                             4096 .
xb@dnxb:/test/dot$ 

它可以正常工作,现在如果我注释掉该continue说明怎么办:

xb@dnxb:/test/dot$ cc /tmp/kr/fsize.c -o /tmp/kr/a.out 
xb@dnxb:/test/dot$ /tmp/kr/a.out .
start
d_name: S..G
hole dot
ice
d_name: S..G
hole dot
ice
d_name: S..G
hole dot
ice
^C
xb@dnxb:/test/dot$

如您所见,我必须使用Ctrl+ C杀死此无限循环程序。

“ ..”目录读取其第一个条目“ ..”并永久循环。

结论:

  1. GNU findutils尝试与* BSD中的find实用程序兼容。

  2. find* BSD中的实用程序内部使用了rmdirPOSIX兼容的C函数,该函数不允许使用点/点-点。

  3. 原因rmdir不允许点/点-点是防止周期性的文件系统路径。

  4. K&R编写的C编程语言显示了点/点-点将如何导致永远循环程序的示例。


16

因为您的find命令返回.结果。从的信息页面rm

尝试删除最后一个文件名成分为“。”的文件。或“ ..”在没有任何提示的情况下被拒绝,这是POSIX要求的。

因此,find在这种情况下,它似乎只是遵循POSIX规则。


2
应当如此:POSIX为王,加上删除当前目录可能会导致一些非常大的问题,具体取决于父应用程序,而其他情况则不会。就像当前目录所在/var/log并且您以root身份运行该目录一样,以为它会删除所有子目录,并且也会删除当前目录呢?
Alexej Magura

1
这是一个很好的理论,但是man页面上显示find:“如果删除失败,则会发出错误消息。” 为什么没有打印错误?
mbroshi

1
@AlexejMagura删除当前目录通常可以正常工作:mkdir foo && cd foo && rmdir $(pwd)。它正在删除.(或..)无效。
塔维安·巴恩斯


2

调用rmdir("."),当我尝试了一个系统调用没有工作,所以没有更高级别的工具可以成功。

您必须通过真实名称而不是.别名删除目录。


1

尽管林果皞和托马斯已经给出了很好的答案,但我感到他们的答案忘记了解释为什么首先要实现这种行为的原因

在您的find . -delete示例中,删除当前目录听起来很合逻辑且理智。但是请考虑:

$ find . -name marti\*
./martin
./martin.jpg
[..]

删除.听起来对您仍然合乎逻辑且理智吗?

删除非空目录是一个错误-因此您不太可能使用find(尽管可以使用rm -r)丢失数据-但是您的shell会将其当前的工作目录设置为不再存在的目录,这会造成一些混乱令人惊讶的行为:

$ pwd
/home/martin/test
$ rm -r ../test 
$ touch foo
touch: cannot touch 'foo': No such file or directory

删除当前目录仅是一种良好的界面设计,并且符合令人惊讶的原则。

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.