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

22

eu esperaria

find . -delete

para excluir o diretório atual, mas não o faz. Por que não?

mbroshi
fonte
3
Provavelmente porque remover o diretório de trabalho atual não seria uma boa ideia.
Alexej Magura
Concordo - eu gosto do comportamento padrão, mas não é consistente com, por exemplo find . -print,.
mbroshi
@AlexejMagura, embora eu simpatize, não vejo por que remover o diretório atual deve ser diferente de remover um arquivo aberto. O objeto permanecerá vivo até que exista uma referência a ele e, em seguida, o lixo será coletado posteriormente. Você pode fazer cd ..; rm -r dircom outro shell com semântica bastante clara ...
Rmano
@Rmano isso é verdade: é apenas algo que eu não faria por princípio: basta subir um diretório e excluir o diretório atual. Não sei ao certo por que isso é tão importante - embora eu tenha tido alguns infortúnios com o diretório atual que não existe mais, como caminhos relativos que não estão mais funcionando, mas você sempre pode sair usando um caminho absoluto - mas uma parte de mim apenas diz que não é uma boa ideia em geral.
Alexej Magura

Respostas:

29

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

Um dos motivos pelos quais ignoramos a exclusão de "." é para compatibilidade com * BSD, onde essa ação se originou.

O NEWS no código fonte 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 ".".

[ATUALIZAR]

Como essa questão se tornou um dos tópicos mais importantes, então eu mergulhei no código-fonte do FreeBSD e saí por uma razão mais convincente.

Vamos ver o código fonte do utilitário find 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 pontos e pontos, alcançará a rmdir()função C definida pelos POSIX's unistd.h.

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

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

Vamos dar uma olhada em como o POSIX descreve o rmdir :

Se o argumento do caminho se referir a um caminho cujo componente final seja ponto ou ponto, rmdir () falhará.

Nenhuma razão foi dada por quê shall fail.

Eu encontrei rename explicar alguns reaso n:

A renomeação de ponto ou ponto é proibida para impedir caminhos cíclicos do sistema de arquivos.

Caminhos cíclicos do sistema de arquivos ?

Eu olho sobre a linguagem de programação C (2nd Edition) e procuro o tópico do diretório, surpreendentemente, achei o código semelhante :

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

E o comentário!

Cada diretório sempre contém entradas próprias, chamadas "." E seu pai, ".."; estes devem ser ignorados ou o programa fará um loop para sempre .

"loop para sempre" , é o mesmo que renamedescrever como "caminhos do sistema de arquivos cíclicos" acima.

Modifico levemente o código e o faço executar no Kali Linux com base nesta 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, e agora se eu comentar a continueinstrução:

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+ Cpara matar esse programa de loop infinito.

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

Conclusão:

  1. O GNU findutilstenta compatível com o findutilitário no * BSD .

  2. findO utilitário * BSD utiliza internamente a rmdirfunção C compatível com POSIX que ponto / ponto-ponto não é permitido.

  3. O motivo de rmdirnão permitir ponto / ponto é impedir caminhos cíclicos do sistema de arquivos.

  4. A linguagem de programação C, escrita por K&R, mostra o exemplo de como ponto / ponto-ponto levará ao programa de loop para sempre.

林果 皞
fonte
16

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

Qualquer tentativa de remover um arquivo cujo último componente de nome de arquivo seja '.' ou '..' é rejeitado sem aviso, conforme exigido pelo POSIX.

Portanto, parece findapenas seguir as regras do POSIX neste caso.

Thomas
fonte
2
Como deveria: POSIX é o rei, além de remover o diretório atual pode causar problemas muito grandes, dependendo do aplicativo pai e do que não. Como se o diretório atual estivesse /var/loge você o executasse como root, pensando que ele removeria todos os subdiretórios e também o diretório atual?
Alexej Magura
1
Essa é uma boa teoria, mas a manpágina para finddiz: "Se a remoção falhar, uma mensagem de erro será emitida". Por que nenhum erro é impresso?
mbroshi
1
@AlexejMagura Removendo o diretório atual fina funciona em geral: mkdir foo && cd foo && rmdir $(pwd). Está removendo .(ou ..) que não funciona.
Tavian Barnes
4

A chamada do sistema rmdir falha com EINVAL se o último componente do caminho do argumento for ".". Está documentado em http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html e a justificativa para o comportamento é:

O significado de excluir nome do caminho / ponto não é claro, porque o nome do arquivo (diretório) no diretório pai a ser removido não é claro, principalmente na presença de vários links para um diretório.

PSkocik
fonte
2

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

Você deve excluir o diretório pelo nome real e não pelo .alias.

Joshua
fonte
1

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

No seu find . -deleteexemplo, excluir o diretório atual parece bastante lógico e sensato. Mas considere:

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

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

Excluir um diretório não vazio é um erro - portanto, é improvável que você perca dados com isso find(embora possa com rm -r) -, mas seu shell terá seu diretório de trabalho atual definido em um diretório que não existe mais, causando confusão. e comportamento 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.

Martin Tournoij
fonte