Почему не находит. удалить текущий каталог?

22

Я бы ожидал

find . -delete

удалить текущий каталог, но это не так. Почему бы нет?

mbroshi
источник
3
Скорее всего, потому что удаление текущего рабочего каталога не будет хорошей идеей.
Алексей Магура
Согласовано - я как поведение по умолчанию, но это не соответствует, например, find . -print.
Мброши
@AlexejMagura, хотя я сочувствую, я не понимаю, почему удаление текущего каталога должно отличаться от удаления открытого файла. Объект останется в живых до тех пор, пока не будет создана ссылка на него, а затем соберет мусор. Вы можете сделать cd ..; rm -r dirс другой оболочкой с довольно четкой семантикой ...
Rmano
@Rmano это правда: это просто то, что я бы не стал делать в принципе: просто зайдите в каталог, а затем удалите текущий каталог. Я не совсем уверен, почему это так важно - хотя у меня были некоторые неудачи с текущим каталогом, который больше не существует, например, относительные пути больше не работают, но вы всегда можете выйти, используя абсолютный путь - но какая-то часть меня просто говорит, что это не очень хорошая идея в целом.
Алексей Магура

Ответы:

29

Члены об этом findutils знают , это для совместимости с * BSD:

Одна из причин, по которой мы пропускаем удаление "." для совместимости с * BSD, где это действие возникло.

NEWS в коде показывает Findutils источника , что они решили сохранить поведение:

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

[ОБНОВИТЬ]

Поскольку этот вопрос стал одной из горячих тем, я углублюсь в исходный код FreeBSD и укажу более убедительную причину.

Давайте посмотрим на утилиту поиска исходного кода 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);
    }
...

Как вы можете видеть, если он не отфильтровывает точку и точку-точку, то он достигнет rmdir()функции C, определенной в POSIX unistd.h.

Сделайте простой тест, rmdir с аргументом точка / точка-точка вернет -1:

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

Давайте посмотрим, как POSIX описывает rmdir :

Если аргумент пути ссылается на путь, конечный компонент которого - точка или точка-точка, rmdir () завершится ошибкой.

Не было дано объяснения почему shall fail.

Я нашел rename объяснить некоторые причины :

Переименование точки или точки-точки запрещено для предотвращения циклических путей файловой системы.

Циклические пути файловой системы ?

Я просматриваю язык программирования C (2-е издание) и ищу тему каталога, неожиданно обнаружил, что код похож :

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

И комментарий!

Каждый каталог всегда содержит записи для себя, называемые ".", И его родитель, ".."; они должны быть пропущены, иначе программа зациклится навсегда .

«цикл навсегда» , это то же самое, что renameописать его как «циклические пути файловой системы» выше.

Я немного модифицирую код и заставляю его работать в Kali Linux на основе этого ответа :

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

Посмотрим:

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$ 

Это работает правильно, теперь, что, если я закомментирую 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$

Как видите, я должен использовать Ctrl+, Cчтобы убить эту программу с бесконечным циклом.

Каталог '..' читает свою первую запись '..' и зацикливается навсегда.

Вывод:

  1. GNU findutilsпопробуйте совместить с findутилитой в * BSD .

  2. findУтилита в * BSD внутренне использует rmdirPOSIX-совместимую функцию C, которую точка / точка-точка не позволяет.

  3. Причина rmdirзапрета точка / точка-точка заключается в предотвращении циклических путей файловой системы.

  4. Язык программирования C, написанный K & R, показывает пример того, как точка / точка-точка приведет к созданию программы цикла навсегда.

林果 皞
источник
16

Потому что ваша findкоманда возвращается .как результат. Со страницы информации rm:

Любая попытка удалить файл с последним компонентом имени файла '.' или «..» отклоняется без каких-либо подсказок, как того требует POSIX.

Таким образом, findв данном случае , похоже, просто придерживаться правил POSIX.

Томас
источник
2
Как и должно быть: POSIX - король, плюс удаление текущего каталога может вызвать очень большие проблемы в зависимости от родительского приложения и того, что нет. Как, если бы текущий каталог был /var/logи вы запустили его как root, думая, что он удалит все подкаталоги, а также удалит текущий каталог?
Алексей Магура
1
Это хорошая теория, но на manстранице findнаписано: «Если удаление не удалось, выдается сообщение об ошибке». Почему не печатается ошибка?
Мброши
1
@AlexejMagura Удаление текущего каталога работает отлично в целом: mkdir foo && cd foo && rmdir $(pwd). Это удаление .(или ..), которое не работает.
Тавиан Барнс
4

Системный вызов rmdir завершается неудачно с EINVAL, если последний компонент его пути аргумента ".". Он задокументирован по адресу http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html, и обоснование такого поведения:

Смысл удаления pathname / dot неясен, поскольку имя файла (каталога) в родительском каталоге, который нужно удалить, неясно, особенно при наличии нескольких ссылок на каталог.

PSkocik
источник
2

Вызов rmdir(".")как системный вызов не сработал, когда я попробовал его, поэтому ни один инструмент более высокого уровня не может быть успешным.

Вы должны удалить каталог через его настоящее имя, а не .псевдоним.

Джошуа
источник
1

Хотя 皞 皞 и Томас уже дали хорошие ответы на этот вопрос, я чувствую, что их ответы забыли объяснить, почему это поведение было реализовано в первую очередь.

В вашем find . -deleteпримере удаление текущего каталога звучит довольно логично и разумно. Но учтите:

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

.Звучит ли удаление по- прежнему логично и нормально для вас?

Удаление непустого каталога является ошибкой - поэтому вы вряд ли потеряете данные с этим find(хотя вы могли бы с этим rm -r) - но в вашей оболочке текущий рабочий каталог будет установлен в каталог, который больше не существует, что приведет к некоторой путанице и удивительное поведение:

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

Не удаляя текущий каталог, это просто хороший дизайн интерфейса и соответствует принципу наименьшего удивления.

Мартин Турной
источник