Por qué no encuentra. -Eliminar eliminar directorio actual?

22

Yo esperaría

find . -delete

para eliminar el directorio actual, pero no lo hace. Por qué no?

mbroshi
fuente
3
Lo más probable es que eliminar el directorio de trabajo actual no sea una buena idea.
Alexej Magura
Acordó - me gusta el comportamiento por defecto, pero no es consistente con, por ejemplo, find . -print.
mbroshi
@AlexejMagura aunque simpatizo, no veo por qué eliminar el directorio actual debería ser diferente de eliminar un archivo abierto. El objeto permanecerá vivo hasta que exista una referencia a él y luego se recolectará la basura. Puedes hacerlo cd ..; rm -r dircon otro caparazón con una semántica bastante clara ...
Rmano
@Rmano, esto es cierto: es algo que no haría en principio: solo sube un directorio y luego elimina el directorio actual. No estoy completamente seguro de por qué es tan importante, aunque he tenido algunas desgracias con el directorio actual que ya no existe, como las rutas relativas que ya no funcionan, pero siempre puedes salir usando una ruta absoluta, pero Una parte de mí dice que no es una buena idea en general.
Alexej Magura

Respuestas:

29

Los miembros de lo findutils saben , es compatible con * BSD:

Una de las razones por las que omitimos la eliminación de "." es por compatibilidad con * BSD, donde se originó esta acción.

Las NOTICIAS en el código fuente de findutils muestran que decidieron mantener el comportamiento:

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

[ACTUALIZAR]

Dado que esta pregunta se convirtió en uno de los temas candentes, me sumergí en el código fuente de FreeBSD y salí a la luz por una razón más convincente.

Veamos el código fuente de la utilidad de búsqueda 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);
    }
...

Como puede ver, si no filtra punto y punto-punto, alcanzará la rmdir()función C definida por POSIX unistd.h.

Haga una prueba simple, rmdir con argumento punto / punto-punto devolverá -1:

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

Echemos un vistazo a cómo POSIX describe rmdir :

Si el argumento de ruta se refiere a una ruta cuyo componente final es punto o punto-punto, rmdir () fallará.

No se dio ninguna razón por qué shall fail.

Encontré rename explicar algunas razones :

Cambiar el nombre de punto o punto-punto está prohibido para evitar rutas cíclicas del sistema de archivos.

¿Rutas cíclicas del sistema de archivos ?

Miro el lenguaje de programación C (2da edición) y busco el tema del directorio, sorprendentemente encontré que el código es similar :

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

Y el comentario!

Cada directorio siempre contiene entradas para sí mismo, llamadas ".", Y su padre, ".."; se deben omitir o el programa se repetirá para siempre .

"bucle para siempre" , esto es lo mismo que renamedescribirlo como "rutas cíclicas del sistema de archivos" arriba.

Modifico ligeramente el código y lo hago correr en Kali Linux basado en esta respuesta :

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

Veamos:

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$ 

Funciona correctamente, ahora qué pasa si comento las continueinstrucciones:

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$

Como puede ver, tengo que usar Ctrl+ Cpara eliminar este programa de bucle infinito.

El directorio '..' lee su primera entrada '..' y se repite para siempre.

Conclusión:

  1. GNU findutilsintenta ser compatible con la findutilidad en * BSD .

  2. findLa utilidad en * BSD utiliza internamente la rmdirfunción C compatible con POSIX que no está permitida dot / dot-dot.

  3. La razón de rmdirno permitir dot / dot-dot es evitar las rutas cíclicas del sistema de archivos.

  4. El lenguaje de programación C escrito por K&R muestra el ejemplo de cómo punto / punto-punto conducirá a un programa de bucle para siempre.

林果 皞
fuente
16

Porque su findcomando regresa .como resultado. Desde la página de información de rm:

Cualquier intento de eliminar un archivo cuyo último componente de nombre de archivo es '.' o '..' se rechaza sin ninguna solicitud, como lo ordena POSIX.

Entonces, parece que findsolo se apega a las reglas POSIX en este caso.

Thomas
fuente
2
Como debería: POSIX es el rey, además de eliminar el directorio actual podría causar algunos problemas muy grandes dependiendo de la aplicación principal y qué no. ¿Qué tal si el directorio actual fuera /var/logy lo ejecutaras como root, pensando que eliminaría todos los subdirectorios y también eliminaría el directorio actual?
Alexej Magura
1
Esa es una buena teoría, pero la manpágina finddice: "Si la eliminación falla, se emite un mensaje de error". ¿Por qué no se imprime ningún error?
mbroshi
1
@AlexejMagura Extracción del directorio actual fina obras en general: mkdir foo && cd foo && rmdir $(pwd). Está eliminando .(o ..) eso no funciona.
Tavian Barnes
4

La llamada al sistema rmdir falla con EINVAL si el último componente de su ruta de argumento es ".". Está documentado en http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html y la justificación del comportamiento es:

El significado de eliminar pathname / dot no está claro, porque el nombre del archivo (directorio) en el directorio principal que se eliminará no está claro, particularmente en presencia de múltiples enlaces a un directorio.

PSkocik
fuente
2

Llamar rmdir(".")como una llamada al sistema no funcionó cuando lo intenté, por lo que ninguna herramienta de nivel superior puede tener éxito.

Debe eliminar el directorio a través de su nombre real, no su .alias.

Joshua
fuente
1

Si bien 林果 皞 y Thomas ya dieron buenas respuestas sobre esto, creo que sus respuestas olvidaron explicar por qué este comportamiento se implementó en primer lugar.

En su find . -deleteejemplo, eliminar el directorio actual suena bastante lógico y sensato. Pero considere:

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

¿ .Todavía te parece lógico y sensato eliminar ?

La eliminación de un directorio no vacío es un error, por lo que es poco probable que pierda datos con esto find(aunque podría hacerlo rm -r), pero su shell tendrá su directorio de trabajo actual configurado en un directorio que ya no existe, lo que puede generar confusión. y comportamiento sorprendente:

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

No eliminar el directorio actual es simplemente un buen diseño de interfaz y cumple con el principio de menor sorpresa.

Martin Tournoij
fuente