Eu entendo que ls -R
exibe uma lista de diretórios. Mas por que é recursivo? Como a recursão é usada no processo?
ls
encontra um diretório, ele recursivamente lista esse diretório.
Eu entendo que ls -R
exibe uma lista de diretórios. Mas por que é recursivo? Como a recursão é usada no processo?
ls
encontra um diretório, ele recursivamente lista esse diretório.
Respostas:
Primeiro, vamos definir uma estrutura de pastas arbitrária:
.
├── 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 o fazemos ls
, obtemos apenas a saída da pasta base:
a1 a2 a3 a4
No entanto, quando ligamos ls -R
, obtemos 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 você pode ver, ele está sendo executado ls
na pasta base e depois em todas as pastas filho. E todas as pastas de netos, ad infinitum. Efetivamente, o comando percorre cada pasta recursivamente até atingir o final da árvore de diretórios. Nesse ponto, ele retorna uma ramificação na árvore e faz o mesmo para todas as subpastas, se houver.
Ou, no 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
}
}
}
E porque eu posso, uma implementação Java de referência do mesmo.
Com efeito, existem duas perguntas intimamente ligadas que você pode estar fazendo.
ls
? Do seu fraseado ("Como a recursão é usada no processo?"), Acho que isso faz parte do que você deseja saber. Esta resposta aborda essa questão.ls
ser implementado com uma técnica recursiva:O FOLDOC define recursão como:
Quando uma função (ou procedimento ) se chama. Essa função é chamada "recursiva". Se a chamada for realizada por meio de uma ou mais funções, esse grupo de funções será chamado "recursivo".
A maneira natural de implementar ls
é escrever uma função que construa uma lista de entradas do sistema de arquivos a serem exibidas e outro código para processar argumentos de caminho e opção e exibir as entradas conforme desejado. É altamente provável que essa função seja implementada recursivamente.
Durante o processamento da opção, ls
determinará se foi solicitado a operar recursivamente (sendo invocado com o -R
sinalizador). Nesse caso, a função que cria uma lista de entradas a serem exibidas se chamará uma vez para cada diretório listado, exceto .
e ..
. Pode haver versões recursivas e não recursivas separadas dessa função, ou a função pode verificar cada vez se é suposto estar operando recursivamente.
O Ubuntu /bin/ls
, o executável que é executado quando você executa ls
, é fornecido pelo GNU Coreutils e possui muitos recursos. Como resultado, seu código é um pouco mais longo e mais complicado do que você imagina. Mas o Ubuntu também contém uma versão mais simples ls
, fornecida pelo BusyBox . Você pode executar isso digitando busybox ls
.
busybox ls
usa a recursão:ls
no BusyBox é implementado em coreutils/ls.c
. Ele contém uma scan_and_display_dirs_recur()
função que é chamada para imprimir uma árvore de diretórios recursivamente:
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);
}
}
}
A linha onde a chamada de função recursiva ocorre é:
scan_and_display_dirs_recur(dnd, 0);
Você pode ver isso em operação se você executar busybox ls
em um depurador. Primeiro instalar os símbolos de depuração por permitindo pacotes -dbgsym.ddeb e, em seguida, instalar o busybox-static-dbgsym
pacote. Instale gdb
também (esse é o depurador).
sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym
Eu sugiro a depuração coreutils ls
em uma árvore de diretórios simples.
Se você não tiver um, faça um (isso funciona da mesma maneira que o mkdir -p
comando na resposta do WinEunuuchs2Unix ):
mkdir -pv foo/{bar/foobar,baz/quux}
E preencha-o com alguns arquivos:
(shopt -s globstar; for d in foo/**; do touch "$d/file$((i++))"; done)
Você pode verificar as busybox ls -R foo
obras conforme o esperado, produzindo esta saída:
foo:
bar baz file0
foo/bar:
file1 foobar
foo/bar/foobar:
file2
foo/baz:
file3 quux
foo/baz/quux:
file4
Abra busybox
no depurador:
gdb busybox
O GDB imprimirá algumas informações sobre si mesmo. Então deve dizer algo como:
Reading symbols from busybox...Reading symbols from /usr/lib/debug/.build-id/5c/e436575b628a8f54c2a346cc6e758d494c33fe.debug...done.
done.
(gdb)
(gdb)
é o seu prompt no depurador. A primeira coisa que você instruirá o GDB a fazer nesse prompt é definir um ponto de interrupção no início da scan_and_display_dirs_recur()
função:
b scan_and_display_dirs_recur
Quando você executa isso, o GDB deve dizer algo como:
Breakpoint 1 at 0x5545b4: file coreutils/ls.c, line 1026.
Agora diga ao GDB para executar busybox
com (ou qualquer nome de diretório que você deseja) como seus argumentos:ls -R foo
run ls -R foo
Você pode ver algo assim:
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 você vir No such file or directory
, como acima, tudo bem. O objetivo desta demonstração é apenas ver quando a scan_and_display_dirs_recur()
função foi chamada, para que o GDB não precise examinar o código-fonte real.
Observe que o depurador atingiu o ponto de interrupção antes mesmo de qualquer entrada de diretório ser impressa. Ele interrompe a entrada dessa função, mas o código nessa função deve ser executado para que todos os diretórios sejam enumerados para impressão.
Para dizer ao GDB para continuar, execute:
c
Cada vez que scan_and_display_dirs_recur()
é chamado, o ponto de interrupção será atingido novamente, para que você veja a recursão em ação. Parece com isso (incluindo o (gdb)
prompt e seus 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]
A função tem recur
seu nome ... o BusyBox a usa somente quando a -R
bandeira é fornecida? No depurador, é fácil descobrir:
(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]
Mesmo sem -R
essa implementação específica, ls
a mesma função é usada para descobrir quais entradas do sistema de arquivos existem e mostrá-las.
Quando você quiser sair do depurador, diga-o:
q
scan_and_display_dirs_recur()
sabe se deve se chamar:Especificamente, como funciona de maneira diferente quando a -R
bandeira é passada? Examinar o código fonte (que pode não ser a versão exata no seu sistema Ubuntu) revela que ele verifica sua estrutura interna de dados G.all_fmt
, onde armazena com quais opções ele foi chamado:
if (ENABLE_FEATURE_LS_RECURSIVE
&& (G.all_fmt & DISP_RECURSIVE)
(Se o BusyBox tiver sido compilado sem suporte -R
, ele também não tentará exibir as entradas do sistema de arquivos recursivamente; é disso que ENABLE_FEATURE_LS_RECURSIVE
se trata a parte.)
Somente quando G.all_fmt & DISP_RECURSIVE
verdadeiro, o código que contém a chamada de função recursiva é executado.
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);
}
Caso contrário, a função será executada apenas uma vez (por diretório especificado na linha de comandos).
Quando você pensa sobre isso, "recursivo" faz sentido para comandos que atuam em diretórios e seus arquivos e diretórios e seus arquivos e diretórios e seus arquivos e diretórios e seus arquivos .........
.... até que toda a árvore do ponto especificado para baixo tenha sido operada pelo comando, neste caso, listando o conteúdo de qualquer subdiretório de qualquer subdiretório de qualquer subdiretório .......... que exista sob o comando argumento (s) do comando
-R é para recursão, que poderia ser chamada "repetidamente".
Veja este código, por exemplo:
───────────────────────────────────────────────────────────────────────────────
$ 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:
Os -p
diretórios em criação permitem criar diretórios em massa com um único comando. Se um ou mais dos diretórios superior-intermediário já existir, não será um erro e os diretórios inferior-médio serão criados.
Em seguida, a ls -R
lista recursiva de todos os diretórios começa com temp e funciona da árvore para todos os ramos.
Agora vamos ver um complemento para o ls -R
comando, ou seja, o tree
comando:
$ tree temp
temp
├── a
├── b
│ └── 1
└── c
└── 1
└── 2
6 directories, 0 files
Como você pode ver tree
realiza o mesmo que ls -R
exceto é mais conciso e ouso dizer "mais bonita".
Agora vamos ver como remover recursivamente os diretórios que acabamos de criar em um comando simples:
$ rm -r temp
Isso remove recursivamente temp
e todos os subdiretórios abaixo dele. ou seja temp/a
, temp/b/1
e temp/c/1/2
mais os diretórios do meio entre os dois.
tree
. É uma ótima ferramenta.
Aqui está uma explicação simples, faz sentido, porque quando se trata de exibir o conteúdo de subdiretórios, a mesma função já sabe o que fazer com um diretório. Portanto, ele só precisa se chamar em cada subdiretório para obter esse resultado!
No pseudocódigo, é algo como isto:
recursive_ls(dir)
print(files and directories)
foreach (directoriy in dir)
recursive_ls(directory)
end foreach
end recursive_ls