Tại sao không tìm thấy. -delete xóa thư mục hiện tại?


22

Tôi mong chờ

find . -delete

để xóa thư mục hiện tại, nhưng nó không. Tại sao không?


3
Rất có thể bởi vì loại bỏ thư mục làm việc hiện tại sẽ không phải là một ý tưởng tốt.
Alexej Magura

Đồng ý - Tôi thích hành vi mặc định, nhưng nó không phù hợp với, ví dụ , find . -print.
mbroshi

@AlexejMagura mặc dù tôi thông cảm, tôi không hiểu tại sao xóa thư mục hiện tại nên khác với xóa tệp đang mở. Đối tượng sẽ tồn tại cho đến khi một tham chiếu đến nó tồn tại, và sau đó rác được thu thập sau đó. Bạn có thể làm cd ..; rm -r dirvới một shell khác với ngữ nghĩa khá rõ ràng ...
Rmano

@Rmano điều này là đúng: đó chỉ là điều tôi không làm theo nguyên tắc: chỉ cần lên một thư mục và sau đó xóa thư mục hiện tại. Tôi không hoàn toàn chắc chắn tại sao nó lại là một vấn đề lớn như vậy - mặc dù tôi đã có một số điều không may với thư mục hiện tại không còn tồn tại, chẳng hạn như các đường dẫn tương đối không còn hoạt động, nhưng bạn luôn có thể thoát ra bằng cách sử dụng một đường dẫn tuyệt đối - nhưng một số phần của tôi chỉ nói rằng đó không phải là một ý tưởng tốt nói chung.
Alexej Magura

Câu trả lời:


29

Các thành viên findutils nhận thức được điều này , nó tương thích với * BSD:

Một trong những lý do khiến chúng tôi bỏ qua việc xóa "." là để tương thích với * BSD, nơi hành động này bắt nguồn.

Các NEWS trong mã nguồn findutils cho thấy rằng họ đã quyết định để giữ cho hành vi:

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

[CẬP NHẬT]

Vì câu hỏi này trở thành một trong những chủ đề nóng, vì vậy tôi đi sâu vào mã nguồn FreeBSD và đưa ra một lý do thuyết phục hơn.

Hãy xem mã nguồn tiện ích của 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);
    }
...

Như bạn có thể thấy, nếu nó không lọc ra dấu chấm và dấu chấm, thì nó sẽ đạt đến rmdir()chức năng C được xác định bởi POSIX unistd.h.

Thực hiện một thử nghiệm đơn giản, rmdir với đối số dot / dot-dot sẽ trả về -1:

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

Hãy xem POSIX mô tả rmdir như thế nào :

Nếu đối số đường dẫn đề cập đến một đường dẫn có thành phần cuối cùng là dấu chấm hoặc dấu chấm, thì rmdir () sẽ thất bại.

Không có lý do được đưa ra tại sao shall fail.

Tôi tìm thấy rename giải thích một số reaso n:

Đổi tên dấu chấm hoặc dấu chấm chấm bị cấm để ngăn chặn đường dẫn hệ thống tệp theo chu kỳ.

Đường dẫn hệ thống tập tin chu kỳ ?

Tôi xem qua Ngôn ngữ lập trình C (Phiên bản 2) và tìm kiếm chủ đề thư mục, thật ngạc nhiên tôi thấy mã này tương tự :

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

Và bình luận!

Mỗi thư mục luôn chứa các mục nhập cho chính nó, được gọi là "." Và cha mẹ của nó, ".."; những cái này phải được bỏ qua, hoặc chương trình sẽ lặp lại mãi mãi .

"Vòng lặp mãi mãi" , điều này giống như cách renamemô tả nó như là "đường dẫn hệ thống tệp theo chu kỳ" ở trên.

Tôi sửa đổi một chút mã và để nó chạy trong Kali Linux dựa trên câu trả lời này :

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

Hãy xem:

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$ 

Nó hoạt động chính xác, bây giờ nếu tôi nhận xét continuehướng dẫn:

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$

Như bạn có thể thấy, tôi phải sử dụng Ctrl+ Cđể giết chương trình vòng lặp vô hạn này.

Thư mục '..' đọc mục đầu tiên '..' và lặp lại mãi mãi.

Phần kết luận:

  1. GNU findutilscố gắng tương thích với findtiện ích trong * BSD .

  2. findtiện ích trong * BSD trong nội bộ sử dụng rmdirchức năng C tương thích POSIX mà không cho phép chấm / chấm-chấm.

  3. Lý do rmdirkhông cho phép dot / dot-dot là ngăn đường dẫn hệ thống tệp theo chu kỳ.

  4. Ngôn ngữ lập trình C được viết bởi K & R cho thấy ví dụ về cách dot / dot-dot sẽ dẫn đến chương trình vòng lặp mãi mãi.


16

Bởi vì findlệnh của bạn trả về .kết quả. Từ trang thông tin của rm:

Mọi nỗ lực xóa tệp có thành phần tên tệp cuối cùng là '.' hoặc '..' bị từ chối mà không có bất kỳ lời nhắc nào, như được ủy quyền bởi POSIX.

Vì vậy, có vẻ như findchỉ cần tuân thủ các quy tắc POSIX trong trường hợp này.


2
Vì nó nên: POSIX là vua, cộng với việc xóa thư mục hiện tại có thể gây ra một số vấn đề rất lớn tùy thuộc vào ứng dụng mẹ và những gì không. Giống như nếu thư mục hiện tại là gì /var/logvà bạn đã chạy nó với quyền root, nghĩ rằng nó sẽ xóa tất cả các thư mục con và nó cũng xóa thư mục hiện tại?
Alexej Magura

1
Đó là một lý thuyết tốt, nhưng mantrang cho findbiết: "Nếu xóa không thành công, một thông báo lỗi được đưa ra." Tại sao không có lỗi in?
mbroshi

1
@AlexejMagura Xóa thư mục hiện tại hoạt động tốt nói chung : mkdir foo && cd foo && rmdir $(pwd). Đó là loại bỏ .(hoặc ..) không hoạt động.
Tavian Barnes

4

Cuộc gọi hệ thống rmdir không thành công với EINVAL nếu thành phần cuối cùng của đường dẫn đối số của nó là ".". Nó được ghi lại tại http://pub.opengroup.org/onlinepub/009695399/fifts/rmdir.html và lý do căn bản cho hành vi là:

Ý nghĩa của việc xóa tên đường dẫn / dấu chấm là không rõ ràng, vì tên của tệp (thư mục) trong thư mục mẹ cần xóa không rõ ràng, đặc biệt là sự hiện diện của nhiều liên kết đến một thư mục.


2

Gọi rmdir(".")như một cuộc gọi hệ thống không hoạt động khi tôi đã thử, vì vậy không có công cụ cấp cao hơn nào có thể thành công.

Bạn phải xóa thư mục thông qua tên thật không phải .bí danh của nó .


1

Trong khi 林果 皞 và Thomas đã đưa ra câu trả lời tốt về vấn đề này, tôi cảm thấy rằng câu trả lời của họ đã quên giải thích lý do tại sao hành vi này được thực hiện ngay từ đầu.

Trong find . -deleteví dụ của bạn, việc xóa thư mục hiện tại nghe có vẻ hợp lý và lành mạnh. Nhưng hãy xem xét:

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

Xóa .vẫn có vẻ hợp lý và lành mạnh với bạn?

Xóa một thư mục không trống là một lỗi - vì vậy bạn không thể mất dữ liệu với điều này find(mặc dù bạn có thể với rm -r) - nhưng shell của bạn sẽ có thư mục làm việc hiện tại được đặt thành một thư mục không còn tồn tại, dẫn đến một số nhầm lẫn và hành vi đáng ngạc nhiên:

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

Không xóa thư mục hiện tại chỉ đơn giản là thiết kế giao diện tốt và tuân thủ nguyên tắc ít gây bất ngờ nhất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.