
Solo quería saber por qué si rm -rf my-symlink
lo uso, elimina solo el enlace simbólico pero rm -rf my-symlink/
elimina los archivos dentro del directorio vinculado y deja el enlace simbólico solo.
Respuesta1
Puede ver la diferencia comparando la salida de stat my-symlink
y stat my-symlink/
. my-symlink
, sin la barra, es el enlace simbólico en sí; my-symlink/
, con la barra diagonal, es el directorio al que apunta el enlace simbólico, que puede verificar por separado comparando los inodos del my-symlink/
directorio al que apunta.
Con esa información en la mano, el comportamiento que estás viendo coincide con el descrito enrm
especificaciones: al procesar un enlace simbólico, rm
elimina el enlace sin descender “dentro” de él si apunta a un directorio; al procesar un directorio (con la -r
opción), elimina su contenido de forma recursiva. En este my-symlink/
caso, rm
intenta eliminar el "directorio", pero falla porque no es un directorio sino un enlace simbólico; sin embargo, debido a la -f
bandera, esto no causa un error.
Respuesta2
Pensé en investigar un poco más el comportamiento, así que aquí hay otra respuesta.
Internamente, rm
utilizaFTSa recursivo en jerarquías de archivos. fts_open
toma una serie de rutas como parámetro y crea una estructura de árbol para cada ruta. Esto permite al programador explorar varias ubicaciones aparentemente como si fueran parte de una jerarquía unificada.
Aquí tienes un programa de prueba que puedes utilizar para jugar con FTS tú mismo.
#include <stdio.h>
#include <stdlib.h>
#include <fts.h>
int main(int argc, char* argv[])
{
if(argc < 2) return EXIT_FAILURE;
char* const* arr = argv + 1;
FTS* hier = fts_open(arr, FTS_NOSTAT | FTS_PHYSICAL, NULL);
FTSENT* ent;
while((ent = fts_read(hier))) {
printf("%s info=%d (D=%d DP=%d F=%d SL=%d)\n",
ent->fts_accpath, ent->fts_info,
ent->fts_info == FTS_D, ent->fts_info == FTS_DP,
ent->fts_info == FTS_F || ent->fts_info == FTS_NSOK,
ent->fts_info == FTS_SL);
}
fts_close(hier);
return EXIT_SUCCESS;
}
Supongamos que hemos creado la siguiente estructura de directorios:
$ mkdir dir
$ touch dir/file
$ ln -s dir sym
Ahora, consideremos su primer caso y veamos cómo FTS lidera la exploración.
$ gcc fts.c
$ ./a.out sym
sym info=12 (D=0 DP=0 F=0 SL=1)
Como puedes ver, en este caso, sym
se ve como un archivo. Un enlace simbólico, para ser más exactos. Con esta información, rm
por lo tanto, lo trataríamos como un archivo y lo llamaríamos unlinkat(AT_FDCWD, "sym", 0)
. El último parámetro (0) hace unlinkat
que se comporte como unlink
. En otras palabras: simplemente elimina un archivo. Como resultado, su enlace desaparece.
Ahora, echemos un vistazo a lo que sucede con sym/
.
$ ./a.out sym/
sym/ info=1 (D=1 DP=0 F=0 SL=0)
file info=11 (D=0 DP=0 F=1 SL=0)
sym/ info=6 (D=0 DP=1 F=0 SL=0)
En este caso, sym
se trató como su directorio de destino. Primero iteramos hasta sym
y sym/file
luego sym
nuevamente. Esto último se debe a cómo funciona FTS: primero, itera sobre el contenido y luego regresa al nodo raíz. En realidad, esto es bastante conveniente para rm
. En la primera pasada ( D
), puede borrar archivos y en la segunda ( DP
) eliminar los directorios vacíos.
Como puede ver, en este caso, FTS informa sym/
como un directorio en ambos casos. Esto se debe a que le dimos a la ruta una barra diagonal, lo que obliga al kernel a interpretarla como un directorio. En el caso de un enlace, esto significa que lo seguirá pase lo que pase.En términos más técnicos, las especificaciones dicen:
Un nombre de ruta que contenga al menos un carácter que no sea una barra y que termine con una o más barras diagonales finales se resolverá como si se agregara un carácter de un solo punto ( '.' ) al nombre de ruta.
Debido a que FTS informa sym/
como un directorio, rm
se comporta como si estuviera eliminando un directorio vacío. En consecuencia, llama unlinkat(AT_FDCWD, "sym/", AT_REMOVEDIR)
. Esto hace unlinkat
que se comporte como rmdir
.
Sin embargo, al resolver la sym/
ruta, la unlinkat
llamada al sistema se dará cuenta de que, en realidad, no se le está asignando un directorio. Por lo tanto, informará ENOTDIR
, lo que desencadena:
$ rm: cannot remove ‘sym/’: Not a directory
Y de hecho, si eliminas la -f
bandera de tus llamadas... Eso es exactamente lo que verás. Ahora bien, si esto es o no un error o una característica... no tengo idea.