Entiendo que ls -Rmuestra una lista de directorios. ¿Pero por qué es recursivo? ¿Cómo se usa la recursión en el proceso?
lsencuentra un directorio, enumera recursivamente ese directorio.
Entiendo que ls -Rmuestra una lista de directorios. ¿Pero por qué es recursivo? ¿Cómo se usa la recursión en el proceso?
lsencuentra 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 lsen 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.lsimplementarse 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 lses 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, lsdeterminará si se le ha pedido que opere recursivamente (al ser invocado con la -Rbandera). 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 lsusa la recursividad:lsen 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 lsen 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-dbgsympaquete. Instalar gdbtambién (ese es el depurador).
sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym
Sugiero depurar coreutils lsen un árbol de directorios simple.
Si no tiene uno a mano, haga uno (esto funciona de la misma manera que el mkdir -pcomando 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 footrabajos 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 busyboxen 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 busyboxcon (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 recursu nombre ... ¿BusyBox solo la usa cuando -Rse 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 lsutiliza 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 -Rse 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_RECURSIVEparte).
Solo cuando G.all_fmt & DISP_RECURSIVEes 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 -pcreació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 -Rrecursivamente enumera todos los directorios, comenzando con temp y trabajando en el árbol hasta todas las ramas.
Ahora veamos un complemento al ls -Rcomando, es decir, el treecomando:
$ tree temp
temp
├── a
├── b
│ └── 1
└── c
└── 1
└── 2
6 directories, 0 files
Como puede ver, treelogra lo mismo que ls -Rexcepto 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 tempy todos los subdirectorios debajo de él. es decir temp/a, temp/b/1y temp/c/1/2má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