Perché non lo trova. -delete cancellare la directory corrente?


22

Mi aspetterei

find . -delete

per eliminare la directory corrente, ma non è così. Perchè no?


3
Molto probabilmente perché rimuovere l'attuale directory di lavoro non sarebbe una buona idea.
Alexej Magura,

Concordato: mi piace il comportamento predefinito, ma non è coerente, ad esempio, con find . -print.
mbroshi,

@AlexejMagura anche se simpatizzo, non vedo perché la rimozione della directory corrente dovrebbe essere diversa dalla rimozione di un file aperto. L'oggetto rimarrà in vita fino a quando non esiste un riferimento ad esso, quindi la spazzatura verrà raccolta successivamente. Puoi fare cd ..; rm -r dircon un'altra shell con semantica abbastanza chiara ...
Rmano,

@Rmano questo è vero: è solo qualcosa che non vorrei fare in linea di principio: basta andare su una directory e quindi eliminare la directory corrente. Non sono del tutto sicuro del perché sia ​​un grosso problema - anche se ho avuto alcune disgrazie con la directory corrente non più esistente, come i percorsi relativi non funzionano più, ma puoi sempre uscire usando un percorso assoluto - ma una parte di me dice solo che non è una buona idea in generale.
Alexej Magura,

Risposte:


29

I membri ne sono a findutils conoscenza , è compatibile con * BSD:

Uno dei motivi per cui saltiamo la cancellazione di "." è per compatibilità con * BSD, dove ha avuto origine questa azione.

Le NEWS nel codice sorgente di findutils mostrano che hanno deciso di mantenere il comportamento:

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

[AGGIORNARE]

Dal momento che questa domanda diventa uno dei temi caldi, quindi mi tuffo nel codice sorgente di FreeBSD e ne esco un motivo più convincente.

Vediamo il codice sorgente dell'utilità di ricerca di 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);
    }
...

Come puoi vedere, se non filtra punto e punto-punto, raggiungerà la rmdir()funzione C definita da POSIX unistd.h.

Fai un semplice test, rmdir con argomento dot / dot-dot restituirà -1:

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

Diamo un'occhiata a come POSIX descrive rmdir :

Se l'argomento path si riferisce a un percorso il cui componente finale è punto o punto-punto, rmdir () fallirà.

Nessun motivo è stato dato il perché shall fail.

Ho trovato rename spiegare alcuni riassunti :

La ridenominazione di punti o punti è vietata per prevenire percorsi ciclici del file system.

Percorsi ciclici del file system ?

Guardo The C Programming Language (2nd Edition) e cerco l' argomento della directory, sorprendentemente ho scoperto che il codice è simile :

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

E il commento!

Ogni directory contiene sempre voci per sé, chiamate ".", E il suo genitore, ".."; questi devono essere saltati, altrimenti il ​​programma si ripeterà per sempre .

"loop per sempre" , è lo stesso di come renamedescriverlo come "percorsi ciclici del file system" sopra.

Modifico leggermente il codice e per farlo funzionare in Kali Linux in base a questa risposta :

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

Vediamo:

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$ 

Funziona correttamente, ora cosa succede se commento le continueistruzioni:

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$

Come puoi vedere, devo usare Ctrl+ Cper uccidere questo programma a ciclo infinito.

La directory '..' legge la sua prima voce '..' e continua per sempre.

Conclusione:

  1. GNU findutilsprova a essere compatibile con l' findutilità in * BSD .

  2. findl'utilità in * BSD utilizza internamente la rmdirfunzione C conforme a POSIX che punto / punto-punto non è consentita.

  3. Il motivo per rmdirnon consentire punto / punto-punto è prevenire percorsi ciclici del file system.

  4. Il linguaggio di programmazione C scritto da K&R mostra l'esempio di come punto / punto-punto porterà al programma per sempre.


16

Perché il tuo findcomando ritorna .come risultato. Dalla pagina di informazioni di rm:

Qualsiasi tentativo di rimuovere un file il cui ultimo componente del nome file è "." o '..' viene rifiutato senza alcuna richiesta, come richiesto da POSIX.

Quindi, sembra findsolo attenersi alle regole POSIX in questo caso.


2
Come dovrebbe: POSIX è il re, inoltre la rimozione della directory corrente potrebbe causare alcuni problemi molto grandi a seconda dell'applicazione padre e cosa no. Ad esempio, se la directory corrente fosse /var/loge la eseguissi come root, pensando che rimuoverà tutti i sottodir e rimuoverà anche la directory corrente?
Alexej Magura,

1
Questa è una buona teoria, ma la manpagina per finddice: "Se la rimozione non è riuscita, viene emesso un messaggio di errore". Perché non viene stampato alcun errore?
mbroshi,

1
@AlexejMagura Rimozione della directory corrente funziona bene in generale: mkdir foo && cd foo && rmdir $(pwd). È rimuovere .(o ..) che non funziona.
Tavian Barnes,

4

La chiamata di sistema rmdir non riesce con EINVAL se è l'ultimo componente del percorso dell'argomento ".". È documentato su http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html e la logica del comportamento è:

Il significato dell'eliminazione di nome percorso / punto non è chiaro, poiché il nome del file (directory) nella directory principale da rimuovere non è chiaro, in particolare in presenza di più collegamenti a una directory.


2

Chiamare rmdir(".")come una chiamata di sistema non ha funzionato quando l'ho provato, quindi nessuno strumento di livello superiore può avere successo.

È necessario eliminare la directory tramite il suo vero nome e non il suo .alias.


1

Mentre 林果 皞 e Thomas hanno già dato buone risposte su questo, sento che le loro risposte hanno dimenticato di spiegare perché questo comportamento è stato implementato in primo luogo.

Nel tuo find . -deleteesempio, l'eliminazione della directory corrente suona piuttosto logica e sana. Ma considera:

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

La cancellazione ti sembra .ancora logica e sana?

L'eliminazione di una directory non vuota è un errore - quindi è improbabile che tu perda dati con questo find(anche se potresti farlo con rm -r) - ma la tua shell avrà la sua directory di lavoro corrente impostata su una directory che non esiste più, portando a un po 'di confusione e comportamento sorprendente:

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

Non eliminare la directory corrente è semplicemente una buona progettazione dell'interfaccia e rispetta il principio della minima sorpresa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.