oczekiwałbym
find . -delete
aby usunąć bieżący katalog, ale tak nie jest. Dlaczego nie?
find . -print
.
cd ..; rm -r dir
z inną powłoką z dość wyraźną semantyką ...
oczekiwałbym
find . -delete
aby usunąć bieżący katalog, ale tak nie jest. Dlaczego nie?
find . -print
.
cd ..; rm -r dir
z inną powłoką z dość wyraźną semantyką ...
Odpowiedzi:
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 rename
opisujemy 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ę continue
instrukcję:
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:
GNU findutils
próbuje kompatybilić z find
narzędziem w * BSD .
find
narzędzie w * BSD wewnętrznie korzysta rmdir
z funkcji C zgodnej z POSIX, której kropka / kropka nie jest dozwolona.
Przyczyną rmdir
niedozwolenia kropka / kropka-kropka jest zapobieganie cyklicznym ścieżkom systemu plików.
Język programowania C napisany przez K&R pokazuje przykład, jak kropka / kropka-kropka doprowadzi do programu na zawsze.
Ponieważ twoje find
polecenie 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 find
w tym przypadku po prostu trzyma się reguł POSIX.
/var/log
i uruchomiłeś go jako root, myśląc, że usunie wszystkie podkatalogi, a także katalog bieżący?
man
strona z find
hasłem mówi: „Jeśli usunięcie się nie powiedzie, zostanie wyświetlony komunikat o błędzie”. Dlaczego nie jest drukowany błąd?
mkdir foo && cd foo && rmdir $(pwd)
. Usuwanie .
(lub ..
), które nie działa.
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.
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 . -delete
przykł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.