Entiendo que ls -R
muestra una lista de directorios. ¿Pero por qué es recursivo? ¿Cómo se usa la recursión en el proceso?
ls
encuentra un directorio, enumera recursivamente ese directorio.
Entiendo que ls -R
muestra una lista de directorios. ¿Pero por qué es recursivo? ¿Cómo se usa la recursión en el proceso?
ls
encuentra un directorio, enumera recursivamente ese directorio.
Respuestas:
En primer lugar, definamos una estructura de carpetas arbitraria:
.
├── a1 [D]
│ ├── b1 [D]
│ │ ├── c1
│ │ ├── c2 [D]
│ │ │ ├── d1
│ │ │ ├── d2
│ │ │ └── d3
│ │ ├── c3
│ │ ├── c4
│ │ └── c5
│ └── b2 [D]
│ ├── c6
│ └── c7
├── a2 [D]
│ ├── b3 [D]
│ │ ├── c8
│ │ └── c9
│ └── b4 [D]
│ ├── c10
│ └── c11
├── a3 [D]
│ ├── b5
│ ├── b6
│ └── b7
└── a4 [D]
Cuando lo hacemos ls
, solo obtenemos la salida de la carpeta base:
a1 a2 a3 a4
Sin embargo, cuando llamamos ls -R
, obtenemos algo diferente:
.:
a1 a2 a3 a4
./a1:
b1 b2
./a1/b1:
c1 c2 c3 c4 c5
./a1/b1/c2:
d1 d2 d3
./a1/b2:
c6 c7
./a2:
b3 b4
./a2/b3:
c8 c9
./a2/b4:
c10 c11
./a3:
b5 b6 b7
./a4:
Como puede ver, se ejecuta ls
en la carpeta base y luego en todas las carpetas secundarias. Y todas las carpetas de nietos, hasta el infinito. Efectivamente, el comando pasa por cada carpeta de forma recursiva hasta que llega al final del árbol de directorios. En ese punto, vuelve a subir una rama en el árbol y hace lo mismo para cualquier subcarpeta, si corresponde.
O, en pseudocódigo:
recursivelyList(directory) {
files[] = listDirectory(directory) // Get all files in the directory
print(directory.name + ":\n" + files.join(" ")) // Print the "ls" output
for (file : files) { // Go through all the files in the directory
if (file.isDirectory()) { // Check if the current file being looked at is a directory
recursivelyList(directory) // If so, recursively list that directory
}
}
}
Y porque puedo, una implementación Java de referencia de la misma.
En efecto, hay dos preguntas estrechamente relacionadas que puede estar haciendo.
ls
? Por tu fraseo ("¿Cómo se usa la recursividad en el proceso?"), Creo que esto es parte de lo que quieres saber. Esta respuesta aborda esa pregunta.ls
implementarse con una técnica recursiva:FOLDOC define la recursividad como:
Cuando una función (o procedimiento ) se llama a sí misma. Dicha función se llama "recursiva". Si la llamada se realiza a través de una o más funciones, este grupo de funciones se llama "recursivo mutuo".
La forma natural de implementar ls
es escribir una función que construya una lista de entradas del sistema de archivos para mostrar, y otro código para procesar argumentos de ruta y opciones y para mostrar las entradas como se desee. Es muy probable que esa función se implemente recursivamente.
Durante el procesamiento de opciones, ls
determinará si se le ha pedido que opere recursivamente (al ser invocado con la -R
bandera). Si es así, la función que crea una lista de entradas para mostrar se llamará a sí misma una vez por cada directorio que enumere, excepto .
y ..
. Puede haber versiones recursivas y no recursivas por separado de esta función, o la función puede verificar cada vez si se supone que está funcionando recursivamente.
Ubuntu's /bin/ls
, el ejecutable que se ejecuta cuando se ejecuta ls
, es proporcionado por GNU Coreutils , y tiene muchas características. Como resultado, su código es algo más largo y más complicado de lo que cabría esperar. Pero Ubuntu también contiene una versión más simple de ls
, proporcionada por BusyBox . Puede ejecutar esto escribiendo busybox ls
.
busybox ls
usa la recursividad:ls
en BusyBox se implementa en coreutils/ls.c
. Contiene una scan_and_display_dirs_recur()
función que se invoca para imprimir un árbol de directorios de forma recursiva:
static void scan_and_display_dirs_recur(struct dnode **dn, int first)
{
unsigned nfiles;
struct dnode **subdnp;
for (; *dn; dn++) {
if (G.all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) {
if (!first)
bb_putchar('\n');
first = 0;
printf("%s:\n", (*dn)->fullname);
}
subdnp = scan_one_dir((*dn)->fullname, &nfiles);
#if ENABLE_DESKTOP
if ((G.all_fmt & STYLE_MASK) == STYLE_LONG || (G.all_fmt & LIST_BLOCKS))
printf("total %"OFF_FMT"u\n", calculate_blocks(subdnp));
#endif
if (nfiles > 0) {
/* list all files at this level */
sort_and_display_files(subdnp, nfiles);
if (ENABLE_FEATURE_LS_RECURSIVE
&& (G.all_fmt & DISP_RECURSIVE)
) {
struct dnode **dnd;
unsigned dndirs;
/* recursive - list the sub-dirs */
dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
if (dndirs > 0) {
dnsort(dnd, dndirs);
scan_and_display_dirs_recur(dnd, 0);
/* free the array of dnode pointers to the dirs */
free(dnd);
}
}
/* free the dnodes and the fullname mem */
dfree(subdnp);
}
}
}
La línea donde tiene lugar la llamada a la función recursiva es:
scan_and_display_dirs_recur(dnd, 0);
Puede ver esto en funcionamiento si se ejecuta busybox ls
en un depurador. En primer lugar instalar los símbolos de depuración por permitir paquetes -dbgsym.ddeb y después de instalar el busybox-static-dbgsym
paquete. Instalar gdb
también (ese es el depurador).
sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym
Sugiero depurar coreutils ls
en un árbol de directorios simple.
Si no tiene uno a mano, haga uno (esto funciona de la misma manera que el mkdir -p
comando en la respuesta de WinEunuuchs2Unix ):
mkdir -pv foo/{bar/foobar,baz/quux}
Y llenarlo con algunos archivos:
(shopt -s globstar; for d in foo/**; do touch "$d/file$((i++))"; done)
Puede verificar los busybox ls -R foo
trabajos como se esperaba, produciendo esta salida:
foo:
bar baz file0
foo/bar:
file1 foobar
foo/bar/foobar:
file2
foo/baz:
file3 quux
foo/baz/quux:
file4
Abrir busybox
en el depurador:
gdb busybox
GDB imprimirá alguna información sobre sí mismo. Entonces debería decir algo como:
Reading symbols from busybox...Reading symbols from /usr/lib/debug/.build-id/5c/e436575b628a8f54c2a346cc6e758d494c33fe.debug...done.
done.
(gdb)
(gdb)
es su aviso en el depurador. Lo primero que le dirá a GDB que haga en este indicador es establecer un punto de interrupción al comienzo de la scan_and_display_dirs_recur()
función:
b scan_and_display_dirs_recur
Cuando ejecuta eso, GDB debería decirle algo como:
Breakpoint 1 at 0x5545b4: file coreutils/ls.c, line 1026.
Ahora dígale a GDB que se ejecute busybox
con (o cualquier nombre de directorio que desee) como sus argumentos:ls -R foo
run ls -R foo
Puede ver algo como esto:
Starting program: /bin/busybox ls -R foo
Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6c60, first=1) at coreutils/ls.c:1026
1026 coreutils/ls.c: No such file or directory.
Si ve No such file or directory
, como arriba, está bien. El propósito de esta demostración es simplemente ver cuándo scan_and_display_dirs_recur()
se ha llamado a la función, por lo que GDB no necesita examinar el código fuente real.
Observe que el depurador alcanzó el punto de interrupción incluso antes de que se imprimieran las entradas de directorio. Se rompe en el entrace a esa función, pero el código de esa función debe funcionar para todos los directorios que se enumeran para imprimir.
Para decirle a GDB que continúe, ejecute:
c
Cada vez que scan_and_display_dirs_recur()
se llama, el punto de interrupción se alcanzará nuevamente, por lo que podrá ver la recursión en acción. Se ve así (incluyendo el (gdb)
indicador y sus comandos):
(gdb) c
Continuing.
foo:
bar baz file0
Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6cb0, first=first@entry=0) at coreutils/ls.c:1026
1026 in coreutils/ls.c
(gdb) c
Continuing.
foo/bar:
file1 foobar
Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6cf0, first=first@entry=0) at coreutils/ls.c:1026
1026 in coreutils/ls.c
(gdb) c
Continuing.
foo/bar/foobar:
file2
foo/baz:
file3 quux
Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6cf0, first=first@entry=0) at coreutils/ls.c:1026
1026 in coreutils/ls.c
(gdb) c
Continuing.
foo/baz/quux:
file4
[Inferior 1 (process 2321) exited normally]
La función tiene recur
su nombre ... ¿BusyBox solo la usa cuando -R
se da la bandera? En el depurador, esto es fácil de descubrir:
(gdb) run ls foo
Starting program: /bin/busybox ls foo
Breakpoint 1, scan_and_display_dirs_recur (dn=dn@entry=0x7e6c60, first=1) at coreutils/ls.c:1026
1026 in coreutils/ls.c
(gdb) c
Continuing.
bar baz file0
[Inferior 1 (process 2327) exited normally]
Incluso sin -R
, esta implementación particular de ls
utiliza la misma función para averiguar qué entradas del sistema de archivos existen y mostrarlas.
Cuando desee salir del depurador, solo dígale:
q
scan_and_display_dirs_recur()
sabe si debería llamarse a sí mismo:Específicamente, ¿cómo funciona de manera diferente cuando -R
se pasa la bandera? El examen del código fuente (que puede no ser la versión exacta en su sistema Ubuntu) revela que verifica su estructura de datos interna G.all_fmt
, donde almacena las opciones con las que se ha invocado:
if (ENABLE_FEATURE_LS_RECURSIVE
&& (G.all_fmt & DISP_RECURSIVE)
(Si BusyBox ha sido compilado sin soporte -R
, tampoco intentará mostrar las entradas del sistema de archivos de forma recursiva; de eso se trata la ENABLE_FEATURE_LS_RECURSIVE
parte).
Solo cuando G.all_fmt & DISP_RECURSIVE
es verdadero se ejecuta el código que contiene la llamada de función recursiva.
struct dnode **dnd;
unsigned dndirs;
/* recursive - list the sub-dirs */
dnd = splitdnarray(subdnp, SPLIT_SUBDIR);
dndirs = count_dirs(subdnp, SPLIT_SUBDIR);
if (dndirs > 0) {
dnsort(dnd, dndirs);
scan_and_display_dirs_recur(dnd, 0);
/* free the array of dnode pointers to the dirs */
free(dnd);
}
De lo contrario, la función solo se ejecuta una vez (por directorio especificado en la línea de comando).
Cuando lo piensa, "recursivo" tiene sentido para los comandos que actúan sobre directorios y sus archivos y directorios y sus archivos y directorios y sus archivos y directorios y sus archivos .........
.... hasta que el comando haya operado todo el árbol desde el punto especificado hacia abajo, en este caso enumerando el contenido de cualquier subdirectorio de cualquier subdirectorio de cualquier subdirectorio .......... que exista bajo el argumento (s) del comando
-R es para recursión, que podría llamarse libremente "repetidamente".
Tome este código por ejemplo:
───────────────────────────────────────────────────────────────────────────────
$ mkdir -p temp/a
───────────────────────────────────────────────────────────────────────────────
$ mkdir -p temp/b/1
───────────────────────────────────────────────────────────────────────────────
$ mkdir -p temp/c/1/2
───────────────────────────────────────────────────────────────────────────────
$ ls -R temp
temp:
a b c
temp/a:
temp/b:
1
temp/b/1:
temp/c:
1
temp/c/1:
2
temp/c/1/2:
La -p
creación de directorios le permite crear directorios en masa con un solo comando. Si ya existen uno o más de los directorios superior-medio, no es un error y se crean los directorios medio-inferior.
Luego, ls -R
recursivamente enumera todos los directorios, comenzando con temp y trabajando en el árbol hasta todas las ramas.
Ahora veamos un complemento al ls -R
comando, es decir, el tree
comando:
$ tree temp
temp
├── a
├── b
│ └── 1
└── c
└── 1
└── 2
6 directories, 0 files
Como puede ver, tree
logra lo mismo que ls -R
excepto es más conciso y me atrevo a decir "más bonito".
Ahora veamos cómo eliminar recursivamente los directorios que acabamos de crear en un comando simple:
$ rm -r temp
Esto elimina de forma recursiva temp
y todos los subdirectorios debajo de él. es decir temp/a
, temp/b/1
y temp/c/1/2
más los directorios intermedios en el medio.
tree
. Es una gran herramienta.
Aquí hay una explicación simple, tiene sentido porque cuando se trata de mostrar el contenido de subdirectorios, la misma función ya sabe qué hacer con un directorio. ¡Por lo tanto, solo necesita llamarse a sí mismo en cada subdirectorio para obtener ese resultado!
En pseudocódigo se parece a esto:
recursive_ls(dir)
print(files and directories)
foreach (directoriy in dir)
recursive_ls(directory)
end foreach
end recursive_ls