Warum findet nicht. -Löschen Sie das aktuelle Verzeichnis?

22

Ich würde erwarten

find . -delete

um das aktuelle Verzeichnis zu löschen, aber nicht. Warum nicht?

mbroshi
quelle
3
Höchstwahrscheinlich, weil das Entfernen des aktuellen Arbeitsverzeichnisses keine gute Idee wäre.
Alexej Magura
Einverstanden - Ich mag das Standardverhalten, aber es ist nicht konsistent mit zB find . -print.
mbroshi
@AlexejMagura Obwohl ich sympathisiere, verstehe ich nicht, warum das Entfernen des aktuellen Verzeichnisses anders sein sollte als das Entfernen einer geöffneten Datei. Das Objekt bleibt am Leben, bis ein Verweis darauf vorhanden ist, und anschließend wird der Müll eingesammelt. Sie können cd ..; rm -r dirmit einer anderen Shell mit ganz klarer Semantik tun ...
Rmano
@Rmano das ist wahr: es ist nur etwas, was ich im Prinzip nicht tun würde: gehe einfach in ein Verzeichnis und lösche dann das aktuelle Verzeichnis. Ich bin mir nicht ganz sicher, warum das so eine große Sache ist - obwohl ich einige Unglücksfälle mit dem aktuellen Verzeichnis hatte, das nicht mehr existiert, wie zum Beispiel, dass relative Pfade nicht mehr funktionieren, aber Sie können immer mit einem absoluten Pfad rauskommen - aber Ein Teil von mir sagt nur, dass es im Allgemeinen keine gute Idee ist.
Alexej Magura

Antworten:

29

Die Mitglieder findutils wissen , dass es mit * BSD kompatibel ist:

Einer der Gründe, warum wir das Löschen von "." dient der Kompatibilität mit * BSD, aus dem diese Aktion stammt.

Der Quellcode von NEWS in findutils zeigt, dass sie beschlossen haben, das Verhalten beizubehalten:

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

[AKTUALISIEREN]

Da diese Frage zu einem der aktuellsten Themen wird, tauche ich in den FreeBSD-Quellcode ein und finde einen überzeugenderen Grund.

Sehen wir uns den Quellcode des Dienstprogramms find von FreeBSD an :

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

Wie Sie sehen, wird die rmdir()durch POSIX definierte C-Funktion erreicht, wenn Punkt und Punkt nicht herausgefiltert werden unistd.h.

Führen Sie einen einfachen Test durch. Rmdir mit dem Argument dot / dot-dot gibt -1 zurück:

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

Schauen wir uns an, wie POSIX rmdir beschreibt :

Wenn sich das path-Argument auf einen Pfad bezieht, dessen letzte Komponente entweder dot oder dot-dot ist, schlägt rmdir () fehl.

Es wurde kein Grund angegeben, warum shall fail.

Ich fand rename einige Gründe zu erklären, n:

Das Umbenennen von Punkt oder Punkt-zu-Punkt ist verboten, um zyklische Dateisystempfade zu verhindern.

Zyklische Dateisystempfade ?

Ich schaue mir die C Programming Language (2nd Edition) an und suche nach einem Verzeichnis-Thema. Überraschenderweise habe ich festgestellt, dass der Code ähnlich ist :

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

Und der Kommentar!

Jedes Verzeichnis enthält immer Einträge für sich selbst mit der Bezeichnung "." Und das übergeordnete Verzeichnis "..". Diese müssen übersprungen werden, sonst wird das Programm für immer wiederholt .

"loop forever" , das ist dasselbe wie oben renameunter "zyklische Dateisystempfade" beschrieben .

Ich habe den Code leicht modifiziert und ihn unter Kali Linux laufen lassen, basierend auf dieser Antwort :

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

Wir werden sehen:

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$ 

Es funktioniert korrekt, was nun, wenn ich die continueAnweisung auskommentiere:

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$

Wie Sie sehen, muss ich Ctrl+ verwenden C, um dieses Endlosschleifenprogramm zu beenden.

Das '..'-Verzeichnis hat seinen ersten Eintrag' .. 'gelesen und wird für immer wiederholt.

Fazit:

  1. GNU findutilsversucht, mit dem findDienstprogramm in * BSD kompatibel zu sein .

  2. findDienstprogramm in * BSD verwendet intern die rmdirPOSIX-kompatible C-Funktion, die Punkt / Punkt-Punkt nicht zulässt.

  3. Der Grund rmdirdafür, dass Punkt / Punkt-Punkt nicht zugelassen wird, besteht darin, dass zyklische Dateisystempfade verhindert werden.

  4. Die von K & R geschriebene Programmiersprache C zeigt beispielhaft, wie Punkt / Punkt-Punkt zu einem Endlosschleifenprogramm führt.

林果 林果
quelle
16

Weil Ihr findBefehl .als Ergebnis zurückgegeben wird. Von der Infoseite von rm:

Jeder Versuch, eine Datei zu entfernen, deren letzte Dateinamenskomponente '.' Ist. oder '..' wird gemäß POSIX-Anweisung ohne Aufforderung abgelehnt.

Es sieht also so aus, als würde man sich findin diesem Fall nur an POSIX-Regeln halten.

Thomas
quelle
2
Wie es sich gehört: POSIX ist König, und das Entfernen des aktuellen Verzeichnisses kann je nach übergeordneter Anwendung und was nicht, einige sehr große Probleme verursachen. Wie wäre es, wenn das aktuelle Verzeichnis wäre /var/logund Sie es als root ausführen würden und denken, es würde alle Unterverzeichnisse und auch das aktuelle Verzeichnis entfernen?
Alexej Magura
1
Das ist eine gute Theorie, aber die manSeite für findsagt: "Wenn das Entfernen fehlgeschlagen ist, wird eine Fehlermeldung ausgegeben." Warum wird kein Fehler gedruckt?
mbroshi
1
@AlexejMagura das aktuelle Verzeichnis Entfernen funktioniert im Allgemeinen: mkdir foo && cd foo && rmdir $(pwd). Es entfernt .(oder ..), was nicht funktioniert.
Tavian Barnes
4

Der Systemaufruf rmdir schlägt mit EINVAL fehl, wenn die letzte Komponente ihres Argumentpfads lautet ".". Es ist unter http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html dokumentiert. Die Gründe für das Verhalten sind:

Die Bedeutung des Löschens von Pfadname / Punkt ist unklar, da der Name der Datei (Verzeichnis) im zu entfernenden übergeordneten Verzeichnis nicht eindeutig ist, insbesondere wenn mehrere Links zu einem Verzeichnis vorhanden sind.

PSkocik
quelle
2

Das Aufrufen rmdir(".")als Systemaufruf hat nicht funktioniert, als ich es ausprobiert habe, sodass kein übergeordnetes Tool erfolgreich sein kann.

Sie müssen das Verzeichnis über den tatsächlichen Namen und nicht über den .Alias löschen .

Joshua
quelle
1

Während 林果 林果 und Thomas bereits gute Antworten dazu gaben, habe ich das Gefühl, dass ihre Antworten vergessen haben, zu erklären, warum dieses Verhalten überhaupt implementiert wurde.

In Ihrem find . -deleteBeispiel klingt das Löschen des aktuellen Verzeichnisses ziemlich logisch und vernünftig. Aber bedenken Sie:

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

Klingt das Löschen für Sie .immer noch logisch und vernünftig?

Das Löschen eines nicht leeren Verzeichnisses ist ein Fehler - es ist also unwahrscheinlich, dass Sie Daten mit verlieren find(obwohl Sie dies mit könnten rm -r) -, aber das aktuelle Arbeitsverzeichnis Ihrer Shell ist auf ein Verzeichnis festgelegt, das nicht mehr existiert, was zu Verwirrung führt und überraschendes Verhalten:

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

Das Löschen des aktuellen Verzeichnisses ist einfach ein gutes Schnittstellendesign und entspricht dem Prinzip der geringsten Überraschung.

Martin Tournoij
quelle