Я б очікував
find . -delete
видалити поточний каталог, але це не зробити. Чому ні?
find . -print
.
cd ..; rm -r dir
ще одну оболонку з цілком чіткою семантикою ...
Я б очікував
find . -delete
видалити поточний каталог, але це не зробити. Чому ні?
find . -print
.
cd ..; rm -r dir
ще одну оболонку з цілком чіткою семантикою ...
Відповіді:
Члени про це 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щоб вбити цю нескінченну програму циклу.
Каталог '..' читає свій перший запис '..' та циклічно назавжди.
Висновок:
GNU findutils
намагається сумісність з find
утилітою в * BSD .
find
утиліта * BSD використовує внутрішню rmdir
функцію C, сумісну з POSIX, яка недоступна точка / точка.
Причиною rmdir
заборонити dot / dot-dot є запобігання циклічних шляхів до файлової системи.
Мова програмування на C, написаний K&R, показує приклад того, як точка / dot-dot призведе до вічно циклічної програми.
Тому що ваша find
команда повертається .
як результат. З інформаційної сторінки rm
:
Будь-яка спроба видалити файл, прізвищем якого прізвище файла є "." або ".." відхиляється без будь-яких спонукань, як це доручено POSIX.
Отже, це виглядає як find
просто дотримується правил POSIX у цьому випадку.
/var/log
і ви запустили його як root, думаючи, що він видалить усі підкаталоги, а також видалить поточний каталог?
man
сторінці для find
написання: "Якщо видалення не вдалося, видається повідомлення про помилку". Чому не друкується помилка?
mkdir foo && cd foo && rmdir $(pwd)
. Видалення .
(або ..
) не працює.
Виклик системи rmdir виходить з ладу з EINVAL, якщо останній компонент шляху його аргументів є "."
. Це задокументовано на веб-сайті http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html,
а обґрунтування поведінки таке:
Сенс видалення імені шляху / крапки незрозумілий, оскільки ім'я файлу (каталогу) у батьківському каталозі, який потрібно видалити, не зрозуміло, особливо за наявності декількох посилань на каталог.
Хоча 林果 皞 і Томас вже дали хороші відповіді на це, я відчуваю, що їхні відповіді забули пояснити, чому така поведінка реалізовується в першу чергу.
У вашому 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
Не видаляти поточний каталог - просто хороший дизайн інтерфейсу і відповідає принципу найменшого здивування.