为啥没找到。 -delete 删除当前目录?

为啥没找到。 -delete 删除当前目录?

我希望

find . -delete

删除当前目录,但事实并非如此。为什么不?

答案1

成员为findutils 意识到这一点,它是为了兼容*BSD:

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

消息findutils 源代码显示他们决定保留该行为:

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

[更新]

由于这个问题成为热门话题之一,因此我深入研究了 FreeBSD 源代码并得出了更有说服力的理由。

让我们看看查找 FreeBSD 实用程序源代码

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 的unistd.h.

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

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

让我们来看看POSIX 如何描述 rmdir

如果路径参数引用的路径的最终组成部分是点或点-点,则 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. GNUfindutils尝试与find实用程序兼容*BSD

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

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

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

答案2

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

任何删除最后一个文件名部分为“.”的文件的尝试或 '..' 在没有任何提示的情况下被拒绝,按照 POSIX 的规定。

因此,在这种情况下,看起来find只是遵循 POSIX 规则。

答案3

"."如果 rmdir 系统调用的参数路径的最后一个组成部分是,则 rmdir 系统调用将失败并返回 EINVAL。它记录在http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html 该行为的理由是:

删除路径名 /dot 的含义不清楚,因为要删除的父目录中的文件(目录)的名称不清楚,特别是在存在多个目录链接的情况下。

答案4

虽然林果皞和Thomas已经对此给出了很好的答案,但我觉得他们的答案忘了解释为什么这种行为首先被实施。

在您的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

不是删除当前目录简直是很好的界面设计,符合最少意外的原则。

相关内容