Pourquoi ne trouve pas. -delete supprimer le répertoire actuel?


22

Je m'attendrais

find . -delete

pour supprimer le répertoire courant, mais ce n'est pas le cas. Pourquoi pas?


3
Très probablement parce que la suppression du répertoire de travail actuel ne serait pas une bonne idée.
Alexej Magura

D' accord - Je aime le comportement par défaut, mais il est pas compatible avec, par exemple, find . -print.
mbroshi

@AlexejMagura bien que je sympathise, je ne vois pas pourquoi la suppression du répertoire actuel devrait être différente de la suppression d'un fichier ouvert. L'objet restera en vie jusqu'à ce qu'il y ait une référence, puis les ordures seront collectées par la suite. Vous pouvez faire cd ..; rm -r diravec un autre shell avec une sémantique assez claire ...
Rmano

@Rmano c'est vrai: c'est juste quelque chose que je ne ferais pas par principe: il suffit de monter un répertoire puis de supprimer le répertoire courant. Je ne sais pas vraiment pourquoi c'est si important - bien que j'ai eu des malheurs avec le répertoire actuel qui n'existe plus, tels que les chemins relatifs ne fonctionnent plus, mais vous pouvez toujours sortir en utilisant un chemin absolu - mais une partie de moi dit simplement que ce n'est pas une bonne idée en général.
Alexej Magura

Réponses:


29

Les membres en sont findutils conscients , c'est pour compatible avec * BSD:

L'une des raisons pour lesquelles nous ignorons la suppression de "." est pour la compatibilité avec * BSD, où cette action a commencé.

Les NOUVELLES du code source de findutils montrent qu'ils ont décidé de conserver le comportement:

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

[MISE À JOUR]

Étant donné que cette question est devenue l'un des sujets d'actualité, je plonge dans le code source de FreeBSD et j'en viens à une raison plus convaincante.

Voyons le code source de l'utilitaire de recherche de 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);
    }
...

Comme vous pouvez le voir, s'il ne filtre pas les points et les points, il atteindra la rmdir()fonction C définie par POSIX unistd.h.

Faites un test simple, rmdir avec l'argument point / point-point retournera -1:

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

Voyons comment POSIX décrit rmdir :

Si l'argument path fait référence à un chemin dont la dernière composante est soit dot soit dot-dot, rmdir () échouera.

Aucune raison n'a été donnée pourquoi shall fail.

J'ai trouvé rename expliquer quelque raison n:

Renommer un point ou un point-point est interdit afin d'empêcher les chemins cycliques du système de fichiers.

Chemins cycliques du système de fichiers ?

Je regarde le langage de programmation C (2e édition) et recherche un sujet de répertoire, étonnamment j'ai trouvé que le code est similaire :

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

Et le commentaire!

Chaque répertoire contient toujours des entrées pour lui-même, appelées ".", Et son parent, ".."; ceux-ci doivent être ignorés, sinon le programme sera en boucle pour toujours .

"boucle pour toujours" , c'est la même chose que la façon de le renamedécrire comme "chemins de système de fichiers cycliques" ci-dessus.

Je modifie légèrement le code et pour le faire fonctionner sous Kali Linux basé sur cette réponse :

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

Voyons voir:

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$ 

Cela fonctionne correctement, maintenant que se passe-t-il si je commente l' continueinstruction:

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$

Comme vous pouvez le voir, je dois utiliser Ctrl+ Cpour tuer ce programme en boucle infinie.

Le répertoire '..' lit sa première entrée '..' et boucle pour toujours.

Conclusion:

  1. GNU findutilsessaie d'être compatible avec l' findutilitaire dans * BSD .

  2. findl'utilitaire dans * BSD utilise en interne la rmdirfonction C conforme POSIX que le point / point-point n'est pas autorisé.

  3. La raison de l' rmdirinterdiction de point / point-point est d'empêcher les chemins cycliques du système de fichiers.

  4. Le langage de programmation C écrit par K&R montre l'exemple de la façon dont point / point-point mènera au programme de boucle pour toujours.


16

Parce que votre findcommande retourne .comme résultat. Depuis la page info de rm:

Toute tentative de suppression d'un fichier dont le dernier composant du nom de fichier est '.' ou '..' est rejeté sans aucune invite, comme mandaté par POSIX.

Ainsi, il semble que findles règles POSIX restent dans ce cas.


2
Comme il se doit: POSIX est roi, plus la suppression du répertoire actuel pourrait causer de très gros problèmes en fonction de l'application parente et non. Comme si le répertoire actuel était /var/loget que vous l'avez exécuté en tant que root, pensant qu'il supprimerait tous les sous-répertoires et qu'il supprimait également le répertoire actuel?
Alexej Magura

1
C'est une bonne théorie, mais la manpage pour finddit: "Si la suppression a échoué, un message d'erreur est émis." Pourquoi aucune erreur n'est-elle imprimée?
mbroshi

1
@AlexejMagura Suppression du répertoire courant fonctionne très bien en général: mkdir foo && cd foo && rmdir $(pwd). C'est la suppression .(ou ..) qui ne fonctionne pas.
Tavian Barnes

4

L'appel système rmdir échoue avec EINVAL si le dernier composant de son chemin d'argument est ".". Il est documenté à http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html et la justification de ce comportement est:

La signification de la suppression du nom de chemin / point n'est pas claire, car le nom du fichier (répertoire) dans le répertoire parent à supprimer n'est pas clair, en particulier en présence de plusieurs liens vers un répertoire.


2

Appel rmdir(".") tant système n'a pas fonctionné lorsque je l'ai essayé, donc aucun outil de niveau supérieur ne peut réussir.

Vous devez supprimer le répertoire par son vrai nom et non par son .alias.


1

Alors que 林果 皞 et Thomas ont déjà donné de bonnes réponses à ce sujet, je pense que leurs réponses ont oublié d'expliquer pourquoi ce comportement a été mis en œuvre en premier lieu.

Dans votre find . -deleteexemple, la suppression du répertoire actuel semble assez logique et sensée. Mais considérez:

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

Supprime . semble- toujours logique et sensée?

La suppression d'un répertoire non vide est une erreur - il est donc peu probable que vous perdiez des données avec cela avec find(bien que vous puissiez le faire avec rm -r) - mais votre shell aura son répertoire de travail actuel défini dans un répertoire qui n'existe plus, ce qui peut prêter à confusion et un comportement surprenant:

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

Ne pas supprimer le répertoire actuel est tout simplement une bonne conception d'interface et est conforme au principe de la moindre surprise.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.