ทำไมไม่หา ลบลบไดเรกทอรีปัจจุบันหรือไม่


22

ฉันคาดหวัง

find . -delete

เพื่อลบไดเรกทอรีปัจจุบัน แต่ทำไม่ได้ ทำไมไม่


3
เป็นไปได้มากว่าเนื่องจากการลบไดเรกทอรีทำงานปัจจุบันจะไม่เป็นความคิดที่ดี
Alexej Magura

ตกลงกัน - ฉันชอบการทำงานเริ่มต้น find . -printแต่มันไม่สอดคล้องกับเช่น
mbroshi

@AlexejMagura แม้ว่าฉันจะเห็นอกเห็นใจฉันไม่เห็นว่าทำไมการลบไดเรกทอรีปัจจุบันควรจะแตกต่างจากการลบไฟล์ที่เปิดอยู่ วัตถุจะยังมีชีวิตอยู่จนกว่าจะมีการอ้างอิงถึงวัตถุนั้นและเก็บขยะในภายหลัง คุณสามารถทำได้cd ..; rm -r dirด้วยเปลือกอื่นที่มีความหมายค่อนข้างชัดเจน ...
Rmano

@Rmano นี่เป็นความจริง: มันเป็นเพียงสิ่งที่ฉันจะไม่ทำบนหลักการ: เพียงแค่ไปที่ไดเรกทอรีแล้วลบไดเรกทอรีปัจจุบัน ฉันไม่แน่ใจทั้งหมดว่าทำไมมันจึงเป็นเรื่องใหญ่ - แม้ว่าฉันจะมีความโชคร้ายบางอย่างกับไดเรกทอรีปัจจุบันไม่มีอยู่อีกต่อไปเช่นเส้นทางญาติไม่ทำงานอีกต่อไป แต่คุณสามารถออกไปได้โดยใช้เส้นทางที่แน่นอน - แต่ บางส่วนของฉันเพิ่งบอกว่ามันไม่ใช่ความคิดที่ดีโดยทั่วไป
Alexej Magura

คำตอบ:


29

สมาชิกที่findutils ตระหนักถึงมันมันเข้ากันได้กับ * BSD:

หนึ่งในเหตุผลที่เราข้ามการลบ "." สำหรับความเข้ากันได้กับ * BSD ซึ่งการกระทำนี้เกิดขึ้น

ข่าวใน 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 กำหนดโดย unistd.hPOSIX

ทำการทดสอบอย่างง่าย rmdir พร้อมอาร์กิวเมนต์ dot / dot-dot จะคืนค่า -1:

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

ลองมาดูกันว่า POSIX อธิบาย rmdir อย่างไร :

หากอาร์กิวเมนต์ของพา ธ อ้างถึงพา ธ ที่มีองค์ประกอบสุดท้ายคือ dot หรือ dot-dot rmdir () จะล้มเหลว

shall failไม่มีเหตุผลว่าทำไม

ฉันพบrename อธิบายบาง reaso n:

ห้ามเปลี่ยนชื่อจุดหรือจุดจุดเพื่อป้องกันเส้นทางของระบบไฟล์วน

เส้นทางระบบไฟล์ที่เป็นวัฏจักร ?

ฉันมองไปที่ภาษาการเขียนโปรแกรม 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 ภายในซึ่งไม่อนุญาตให้ใช้ dot / dot-dot

  3. เหตุผลของการrmdirไม่อนุญาตให้ dot / dot-dot คือการป้องกันเส้นทางระบบไฟล์วงจร

  4. ภาษาการเขียนโปรแกรม C ที่เขียนโดย K&R แสดงตัวอย่างว่า dot / dot-dot จะนำไปสู่โปรแกรมวนซ้ำตลอดไปอย่างไร


16

เพราะfindคำสั่งของคุณกลับ.มาเป็นผล จากหน้าข้อมูลของrm:

ความพยายามที่จะลบไฟล์ที่มีส่วนประกอบของชื่อไฟล์สุดท้ายคือ '.' หรือ '.. ' ถูกปฏิเสธโดยไม่ต้องแจ้งใด ๆ ตามที่กำหนดโดย POSIX

ดังนั้นดูเหมือนว่าfindจะยึดติดกับกฎ POSIX ในกรณีนี้


2
อย่างที่ควรจะเป็น: POSIX เป็นราชารวมถึงการลบไดเรกทอรีปัจจุบันอาจทำให้เกิดปัญหาใหญ่มากขึ้นอยู่กับแอปพลิเคชันหลักและสิ่งที่ไม่ เช่นถ้าไดเรกทอรีปัจจุบันเป็น/var/logและคุณเรียกใช้เป็น root คิดว่ามันจะลบส่วนย่อยทั้งหมดและลบไดเรกทอรีปัจจุบันด้วยหรือไม่
Alexej Magura

1
นั่นเป็นทฤษฎีที่ดี แต่manหน้าสำหรับfindพูดว่า: "หากการลบล้มเหลวข้อความแสดงข้อผิดพลาดจะปรากฏขึ้น" ทำไมไม่มีข้อผิดพลาดพิมพ์ออกมา?
mbroshi

1
@AlexejMagura mkdir foo && cd foo && rmdir $(pwd)ถอดไดเรกทอรีปัจจุบันทำงานได้ดีโดยทั่วไป: มันกำลังลบ.(หรือ..) ที่ใช้งานไม่ได้
Tavian Barnes

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

การไม่ลบไดเรกทอรีปัจจุบันเป็นการออกแบบอินเตอร์เฟสที่ดีและสอดคล้องกับหลักการของความประหลาดใจน้อยที่สุด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.