Чому не знаходять. - видалити поточний каталог?


22

Я б очікував

find . -delete

видалити поточний каталог, але це не зробити. Чому ні?


3
Швидше за все, тому що видалення поточного робочого каталогу не було б гарною ідеєю.
Олексій Магура

Погоджено - я як поведінка за умовчанням, але це не відповідає, наприклад, find . -print.
mbroshi

@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 з аргументом dot / dot поверне -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 використовує внутрішню rmdirфункцію C, сумісну з POSIX, яка недоступна точка / точка.

  3. Причиною rmdirзаборонити dot / dot-dot є запобігання циклічних шляхів до файлової системи.

  4. Мова програмування на C, написаний K&R, показує приклад того, як точка / dot-dot призведе до вічно циклічної програми.


16

Тому що ваша findкоманда повертається .як результат. З інформаційної сторінки rm:

Будь-яка спроба видалити файл, прізвищем якого прізвище файла є "." або ".." відхиляється без будь-яких спонукань, як це доручено POSIX.

Отже, це виглядає як findпросто дотримується правил POSIX у цьому випадку.


2
Як слід: POSIX є королем, плюс видалення поточного каталогу може спричинити дуже великі проблеми залежно від батьківського додатку та чого ні. Як, що робити, якщо поточний каталог був, /var/logі ви запустили його як root, думаючи, що він видалить усі підкаталоги, а також видалить поточний каталог?
Олексій Магура

1
Це гарна теорія, але на manсторінці для findнаписання: "Якщо видалення не вдалося, видається повідомлення про помилку". Чому не друкується помилка?
mbroshi

1
@AlexejMagura Видалення поточного каталогу працює відмінно в цілому: mkdir foo && cd foo && rmdir $(pwd). Видалення .(або ..) не працює.
Тавіан Барнс

4

Виклик системи rmdir виходить з ладу з EINVAL, якщо останній компонент шляху його аргументів є ".". Це задокументовано на веб-сайті http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html, а обґрунтування поведінки таке:

Сенс видалення імені шляху / крапки незрозумілий, оскільки ім'я файлу (каталогу) у батьківському каталозі, який потрібно видалити, не зрозуміло, особливо за наявності декількох посилань на каталог.


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

Не видаляти поточний каталог - просто хороший дизайн інтерфейсу і відповідає принципу найменшого здивування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.