Por que não encontra. -eliminar o diretório atual?

22

Eu esperaria

find . -delete

para excluir o diretório atual, mas isso não acontece. Porque não?

    
por mbroshi 18.11.2016 / 15:11

5 respostas

28

Os membros de findutils sabem disso , são compatíveis com * BSD:

One of the reasons that we skip deletion of "." is for compatibility with *BSD, where this action originated.

O NEWS no código-fonte do findutils mostra que eles decidiram manter o comportamento:

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

[UPDATE]

Como esta questão se torna um dos assuntos mais importantes, então eu mergulho no código fonte do FreeBSD e vejo uma razão mais convincente.

Vamos ver o código fonte do utilitário do 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);
    }
...

Como você pode ver, se não filtrar ponto e ponto, ele alcançará a função rmdir() C definida pelo unistd.h do POSIX.

Faça um teste simples, rmdir com argumento dot / dot-dot retornará -1:

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

Vamos dar uma olhada como o POSIX descreve o rmdir :

If the path argument refers to a path whose final component is either dot or dot-dot, rmdir() shall fail.

Nenhuma razão foi dada porque shall fail .

Eu encontrei rename explico alguns argumentos n:

Renaming dot or dot-dot is prohibited in order to prevent cyclical file system paths.

Caminhos do sistema de arquivos cíclicos ?

Eu olho A Linguagem de Programação C (2ª Edição) e procuro pelo tópico do diretório, surpreendentemente eu encontrei o código é semelhante :

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

E o comentário!

Each directory always contains entries for itself, called ".", and its parent, ".."; these must be skipped, or the program will loop forever.

"loop para sempre" , é como rename descreve como "caminhos do sistema de arquivos cíclicos" acima.

Eu modifico um pouco o código e o faço executar no Kali Linux com base em esta resposta :

#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);
}

Vamos ver:

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$ 

Funciona corretamente, agora, se eu comentar a instrução 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$

Como você pode ver, eu tenho que usar Ctrl + C para matar este programa de loop infinitamente.

O diretório '..' lê sua primeira entrada '..' e faz um loop para sempre.

Conclusão:

  1. O GNU findutils tenta ser compatível com o utilitário find em * BSD .

  2. find utility no * BSD usa internamente rmdir função C compatível com POSIX que ponto / ponto-ponto não é permitido.

  3. O motivo de rmdir não permitir que o ponto / ponto seja impedido de seguir caminhos cíclicos do sistema de arquivos.

  4. A linguagem de programação C escrita por K & R mostra o exemplo de como o ponto / ponto-ponto levará a um programa de loop contínuo.

por 18.11.2016 / 17:00
16

Porque seu comando find retorna . como resultado. Na página de informações de rm :

Any attempt to remove a file whose last file name component is ‘.’ or ‘..’ is rejected without any prompting, as mandated by POSIX.

Portanto, parece que find apenas se atém às regras POSIX neste caso.

    
por 18.11.2016 / 16:23
4

A chamada do sistema rmdir falha com EINVAL se o último componente do caminho do argumento for "." . Está documentado no link e a razão para o comportamento é:

The meaning of deleting pathname /dot is unclear, because the name of the file (directory) in the parent directory to be removed is not clear, particularly in the presence of multiple links to a directory.

    
por 18.11.2016 / 23:16
2

A chamada de rmdir(".") como uma chamada de sistema não funcionou quando eu tentei, portanto nenhuma ferramenta de nível superior pode ser bem-sucedida.

Você deve excluir o diretório por meio de seu nome real e não por . alias.

    
por 18.11.2016 / 23:09
1

Enquanto 林果 皞 e Thomas já deram boas respostas sobre isso, eu sinto que suas respostas se esqueceram de explicar porque esse comportamento foi implementado em primeiro lugar.

No seu exemplo find . -delete , a exclusão do diretório atual parece bastante lógica e sensata. Mas considere:

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

A exclusão de . ainda parece lógica e sensata para você?

Excluir um diretório não vazio é um erro - assim, é improvável que você perca dados com isso com find (embora você possa com rm -r ), mas seu shell terá seu diretório de trabalho atual definido como um diretório que não existem mais, levando a um comportamento confuso e surpreendente:

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

Não excluir o diretório atual é simplesmente um bom design de interface e está em conformidade com o princípio da menor surpresa.

    
por 19.11.2016 / 02:34