Capisco che ls -R
visualizza un elenco di directory. Ma perché è ricorsivo? Come viene utilizzata la ricorsione nel processo?
ls
incontra una directory elenca ricorsivamente quella directory.
Capisco che ls -R
visualizza un elenco di directory. Ma perché è ricorsivo? Come viene utilizzata la ricorsione nel processo?
ls
incontra una directory elenca ricorsivamente quella directory.
Risposte:
Prima di tutto, definiamo una struttura di cartelle 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]
Quando lo facciamo ls
, otteniamo solo l'output della cartella di base:
a1 a2 a3 a4
Tuttavia, quando chiamiamo ls -R
, otteniamo qualcosa di diverso:
.:
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:
Come puoi vedere, è in esecuzione ls
sulla cartella di base e quindi su tutte le cartelle secondarie. E tutte le cartelle dei nipoti, all'infinito. In effetti, il comando attraversa ricorsivamente ogni cartella fino a quando non raggiunge la fine dell'albero delle directory. A quel punto, ritorna su un ramo nella struttura e fa la stessa cosa per qualsiasi sottocartella, se presente.
Oppure, in pseudo-codice:
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
}
}
}
E poiché posso, un'implementazione Java di riferimento della stessa.
Ci sono, in effetti, due domande strettamente collegate che potresti porre.
ls
? Dal tuo fraseggio ("Come viene utilizzata la ricorsione nel processo?"), Penso che questo sia parte di ciò che vuoi sapere. Questa risposta risponde a questa domanda.ls
essere implementato con una tecnica ricorsiva:FOLDOC definisce la ricorsione come:
Quando una funzione (o procedura ) si chiama da sola. Tale funzione è chiamata "ricorsiva". Se la chiamata avviene tramite una o più altre funzioni, questo gruppo di funzioni viene chiamato "reciprocamente ricorsivo".
Il modo naturale di implementare ls
è scrivere una funzione che costruisce un elenco di voci del filesystem da visualizzare, e altro codice per elaborare il percorso e gli argomenti delle opzioni e per visualizzare le voci come desiderato. È molto probabile che tale funzione venga implementata in modo ricorsivo.
Durante l'elaborazione dell'opzione, ls
determinerà se è stato chiesto di operare in modo ricorsivo (invocando il -R
flag). In tal caso, la funzione che crea un elenco di voci da visualizzare si chiamerà una volta per ogni directory che elenca, tranne .
e ..
. Potrebbero esistere versioni separate ricorsive e non ricorsive di questa funzione, oppure la funzione può verificare ogni volta se si suppone che funzioni in modo ricorsivo.
Ubuntu /bin/ls
, l'eseguibile che viene eseguito durante l'esecuzione ls
, è fornito da GNU Coreutils e ha molte funzionalità. Di conseguenza, il suo codice è un po 'più lungo e più complicato di quanto ci si possa aspettare. Ma Ubuntu contiene anche una versione più semplice di ls
, fornita da BusyBox . Puoi eseguirlo digitando busybox ls
.
busybox ls
utilizza la ricorsione:ls
in BusyBox è implementato in coreutils/ls.c
. Contiene una scan_and_display_dirs_recur()
funzione che viene invocata per stampare ricorsivamente un albero di directory:
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 linea in cui ha luogo la chiamata di funzione ricorsiva è:
scan_and_display_dirs_recur(dnd, 0);
Puoi vederlo in funzione se esegui busybox ls
un debugger. In primo luogo installare i simboli di debug da consentendo pacchetti -dbgsym.ddeb e quindi l'installazione del busybox-static-dbgsym
pacchetto. Installa gdb
pure (questo è il debugger).
sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym
Suggerisco il debug coreutils ls
su un semplice albero di directory.
Se non ne hai uno a portata di mano, creane uno (funziona allo stesso modo del mkdir -p
comando nella risposta di WinEunuuchs2Unix ):
mkdir -pv foo/{bar/foobar,baz/quux}
E popolarlo con alcuni file:
(shopt -s globstar; for d in foo/**; do touch "$d/file$((i++))"; done)
Puoi verificare che busybox ls -R foo
funzioni come previsto, producendo questo output:
foo:
bar baz file0
foo/bar:
file1 foobar
foo/bar/foobar:
file2
foo/baz:
file3 quux
foo/baz/quux:
file4
Apri busybox
nel debugger:
gdb busybox
GDB stamperà alcune informazioni su se stesso. Quindi dovrebbe dire qualcosa del tipo:
Reading symbols from busybox...Reading symbols from /usr/lib/debug/.build-id/5c/e436575b628a8f54c2a346cc6e758d494c33fe.debug...done.
done.
(gdb)
(gdb)
è il tuo prompt nel debugger. La prima cosa che dirai a GDB di fare su questo prompt è impostare un breakpoint all'inizio della scan_and_display_dirs_recur()
funzione:
b scan_and_display_dirs_recur
Quando lo esegui, GDB dovrebbe dirti qualcosa del tipo:
Breakpoint 1 at 0x5545b4: file coreutils/ls.c, line 1026.
Ora dì a GDB di funzionare busybox
con (o qualunque sia il nome della directory che desideri) come argomenti:ls -R foo
run ls -R foo
Potresti vedere qualcosa del genere:
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.
Se vedi No such file or directory
, come sopra, va bene. Lo scopo di questa dimostrazione è solo vedere quando scan_and_display_dirs_recur()
è stata chiamata la funzione, quindi GDB non ha bisogno di esaminare il codice sorgente effettivo.
Si noti che il debugger ha raggiunto il punto di interruzione anche prima che tutte le voci della directory fossero stampate. Si interrompe sull'ingresso a quella funzione, ma il codice in quella funzione deve essere eseguito affinché tutte le directory siano enumerate per la stampa.
Per dire a GDB di continuare, esegui:
c
Ogni volta che scan_and_display_dirs_recur()
viene chiamato, il punto di interruzione verrà colpito di nuovo, quindi vedrai la ricorsione in azione. Sembra così (incluso il (gdb)
prompt e i tuoi comandi):
(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 funzione ha recur
nel suo nome ... BusyBox la usa solo quando -R
viene dato il flag? Nel debugger, questo è facile da scoprire:
(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]
Anche senza -R
, questa particolare implementazione di ls
usa la stessa funzione per scoprire quali voci del filesystem esistono e mostrarle.
Quando si desidera uscire dal debugger, basta dirlo:
q
scan_and_display_dirs_recur()
sa se dovrebbe chiamarsi:In particolare, come funziona in modo diverso quando -R
viene passata la bandiera? Esaminare il codice sorgente (che potrebbe non essere la versione esatta sul tuo sistema Ubuntu) rivela che controlla la sua struttura interna di dati G.all_fmt
, dove memorizza le opzioni con cui è stato invocato:
if (ENABLE_FEATURE_LS_RECURSIVE
&& (G.all_fmt & DISP_RECURSIVE)
(Se BusyBox è stato compilato senza supporto per -R
, allora non tenterà nemmeno di visualizzare le voci del filesystem in modo ricorsivo; questo è ciò di cui tratta la ENABLE_FEATURE_LS_RECURSIVE
parte.)
Solo quando G.all_fmt & DISP_RECURSIVE
è vero viene eseguito il codice che contiene la chiamata di funzione ricorsiva.
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);
}
Altrimenti, la funzione viene eseguita una sola volta (per directory specificata nella riga di comando).
Quando ci pensi, "ricorsivo" ha senso per i comandi che agiscono sulle directory e sui loro file e directory e sui loro file e directory e sui loro file e directory e i loro file .........
.... fino a quando l'intero albero dal punto specificato in basso non è stato operato dal comando, in questo caso elencando il contenuto di tutte le sottodirectory di qualsiasi sottodirectory di qualsiasi sottodirectory .......... che esistono sotto il argomento (i) del comando
-R è per la ricorsione, che potrebbe essere liberamente definito "ripetutamente".
Prendi questo codice per esempio:
───────────────────────────────────────────────────────────────────────────────
$ 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:
Le -p
directory di creazione consentono di creare in serie directory con un solo comando. Se esiste già una o più delle directory medio-alte non è un errore e vengono create le directory medio-basse.
Quindi ls -R
elenca in modo ricorsivo ogni singola directory che inizia con temp e funziona in modo discendente dall'albero verso tutti i rami.
Ora diamo un'occhiata a un complemento del ls -R
comando, ovvero il tree
comando:
$ tree temp
temp
├── a
├── b
│ └── 1
└── c
└── 1
└── 2
6 directories, 0 files
Come puoi vedere, tree
lo stesso che ls -R
tranne è più conciso e oso dire "più bello".
Ora diamo un'occhiata a come rimuovere ricorsivamente le directory che abbiamo appena creato con un semplice comando:
$ rm -r temp
Questo rimuove ricorsivamente temp
e tutte le sottodirectory sottostanti. vale a dire temp/a
, temp/b/1
e in temp/c/1/2
più le directory intermedie in mezzo.
tree
però. È un ottimo strumento.
Ecco una semplice spiegazione, ha senso perché quando si tratta di visualizzare il contenuto delle sottodirectory, la stessa funzione sa già cosa fare con una directory. Pertanto deve solo richiamare se stesso in ogni sottodirectory per ottenere quel risultato!
In pseudocodice sembra qualcosa del genere:
recursive_ls(dir)
print(files and directories)
foreach (directoriy in dir)
recursive_ls(directory)
end foreach
end recursive_ls