Dlaczego nie można znaleźć -usunąć usunąć bieżący katalog?

22

oczekiwałbym

find . -delete

aby usunąć bieżący katalog, ale tak nie jest. Dlaczego nie?

mbroshi
źródło
3
Najprawdopodobniej usunięcie bieżącego katalogu roboczego nie byłoby dobrym pomysłem.
Alexej Magura,
Uzgodnione - podoba mi się zachowanie domyślne, ale nie jest zgodne z np find . -print.
mbroshi,
@AlexejMagura chociaż sympatyzuję, nie rozumiem, dlaczego usunięcie bieżącego katalogu powinno być inne niż usunięcie otwartego pliku. Obiekt pozostanie przy życiu, dopóki nie będzie odniesienia do niego, a następnie zostaną usunięte śmieci. Możesz zrobić cd ..; rm -r dirz inną powłoką z dość wyraźną semantyką ...
Rmano,
@Rmano to prawda: to po prostu coś, czego nie zrobiłbym w zasadzie: po prostu przejdź do katalogu, a następnie usuń bieżący katalog. Nie jestem do końca pewien, dlaczego to taka wielka sprawa - chociaż miałem pewne nieszczęścia, że ​​bieżący katalog już nie istnieje, takie jak ścieżki względne, które już nie działają, ale zawsze możesz wydostać się, używając ścieżki bezwzględnej - ale jakaś część mnie mówi, że ogólnie nie jest to dobry pomysł.
Alexej Magura,

Odpowiedzi:

29

Członkowie findutils świadomi tego , jest to zgodne z * BSD:

Jednym z powodów, dla których pomijamy usunięcie „”. dotyczy zgodności z * BSD, z którego pochodzi ta akcja.

NEWS w kodzie źródłowym Findutils pokazuje, że zdecydowali się zachować zachowanie:

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

[AKTUALIZACJA]

Ponieważ pytanie to stało się jednym z gorących tematów, zagłębiam się w kod źródłowy FreeBSD i podam bardziej przekonujący powód.

Zobaczmy kod źródłowy narzędzia 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);
    }
...

Jak widać, jeśli nie odfiltruje kropki i kropki, osiągnie rmdir()funkcję C zdefiniowaną przez POSIX unistd.h.

Wykonaj prosty test, rmdir z argumentem kropka / kropka-kropka zwróci -1:

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

Zobaczmy, jak POSIX opisuje rmdir :

Jeśli argument ścieżki odnosi się do ścieżki, której końcowym składnikiem jest kropka lub kropka-kropka, rmdir () zawiedzie.

Nie podano powodu shall fail.

Znalazłem rename wyjaśnienie kilku powodów :

Zmiana nazwy kropki lub kropki jest zabroniona, aby zapobiec cyklicznym ścieżkom systemu plików.

Cykliczne ścieżki systemu plików ?

Spoglądam na język programowania C (wydanie drugie) i szukam tematu katalogu, co zaskakujące, stwierdziłem, że kod jest podobny :

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

I komentarz!

Każdy katalog zawsze zawiera wpisy dla siebie, zwane „.”, I jego element nadrzędny „..”; należy je pominąć, inaczej program zapętli się na zawsze .

„pętla na zawsze” , to tak samo, jak renameopisujemy to jako „cykliczne ścieżki systemu plików” powyżej.

Lekko modyfikuję kod i uruchamiam go w Kali Linux na podstawie tej odpowiedzi :

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

Zobaczmy:

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$ 

Działa poprawnie, co teraz, jeśli skomentuję continueinstrukcję:

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$

Jak widać, muszę użyć Ctrl+, Caby zabić ten program nieskończonej pętli.

Katalog „..” czyta swój pierwszy wpis „..” i zapętla na zawsze.

Wniosek:

  1. GNU findutilspróbuje kompatybilić z findnarzędziem w * BSD .

  2. findnarzędzie w * BSD wewnętrznie korzysta rmdirz funkcji C zgodnej z POSIX, której kropka / kropka nie jest dozwolona.

  3. Przyczyną rmdirniedozwolenia kropka / kropka-kropka jest zapobieganie cyklicznym ścieżkom systemu plików.

  4. Język programowania C napisany przez K&R pokazuje przykład, jak kropka / kropka-kropka doprowadzi do programu na zawsze.

林果 皞
źródło
16

Ponieważ twoje findpolecenie zwraca .jako wynik. Ze strony informacyjnej rm:

Każda próba usunięcia pliku, którego ostatnią składową nazwy pliku jest „.” lub „..” jest odrzucane bez monitowania, zgodnie z mandatem POSIX.

Wygląda na to, że findw tym przypadku po prostu trzyma się reguł POSIX.

Tomasz
źródło
2
Tak jak powinno: POSIX jest królem, a usunięcie bieżącego katalogu może spowodować bardzo duże problemy w zależności od aplikacji nadrzędnej, a co nie. Jak gdyby bieżący katalog był /var/logi uruchomiłeś go jako root, myśląc, że usunie wszystkie podkatalogi, a także katalog bieżący?
Alexej Magura,
1
To dobra teoria, ale manstrona z findhasłem mówi: „Jeśli usunięcie się nie powiedzie, zostanie wyświetlony komunikat o błędzie”. Dlaczego nie jest drukowany błąd?
mbroshi,
1
@AlexejMagura Usuwanie bieżący katalog działa dobrze w ogóle: mkdir foo && cd foo && rmdir $(pwd). Usuwanie .(lub ..), które nie działa.
Tavian Barnes
4

Wywołanie systemowe rmdir kończy się niepowodzeniem z EINVAL, jeśli ostatnim składnikiem ścieżki argumentu jest ".". Jest to udokumentowane na stronie http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html, a uzasadnienie takiego zachowania jest następujące:

Znaczenie usuwania ścieżki / kropki jest niejasne, ponieważ nazwa pliku (katalogu) w katalogu nadrzędnym, który ma zostać usunięty, nie jest jasna, szczególnie w obecności wielu łączy do katalogu.

PSkocik
źródło
2

Wywołanie rmdir(".")jako wywołanie systemowe nie działało, kiedy próbowałem, więc żadne narzędzie wyższego poziomu nie może odnieść sukcesu.

Musisz usunąć katalog, używając jego prawdziwej nazwy, a nie .aliasu.

Jozuego
źródło
1

Chociaż 林果 皞 i Thomas już udzielili na to dobrych odpowiedzi, wydaje mi się, że ich odpowiedzi zapomniały wyjaśnić, dlaczego to zachowanie zostało wdrożone w pierwszej kolejności.

W twoim find . -deleteprzykładzie usunięcie bieżącego katalogu brzmi dość logicznie i rozsądnie. Ale zastanów się:

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

Czy usunięcie .nadal wydaje ci się logiczne i rozsądne?

Usunięcie niepustego katalogu jest błędem - więc jest mało prawdopodobne, że stracisz przy tym dane find(chociaż możesz to zrobić rm -r) - ale w twojej powłoce będzie ustawiony bieżący katalog roboczy na katalog, który już nie istnieje, co może powodować mylące i zaskakujące zachowanie:

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

Nie usuwając bieżący katalog jest po prostu dobry design interfejsu i jest zgodny z zasadą najmniejszego zaskoczenia.

Martin Tournoij
źródło