Я так понимаю, что ls -R
отображает список каталогов. Но почему это рекурсивно? Как в процессе используется рекурсия?
ls
встречает каталог, он рекурсивно перечисляет этот каталог.
Я так понимаю, что ls -R
отображает список каталогов. Но почему это рекурсивно? Как в процессе используется рекурсия?
ls
встречает каталог, он рекурсивно перечисляет этот каталог.
Ответы:
Прежде всего, давайте определим произвольную структуру папок:
.
├── 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]
Когда мы это сделаем ls
, мы получим вывод только базовой папки:
a1 a2 a3 a4
Однако когда мы звоним ls -R
, мы получаем что-то другое:
.:
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:
Как видите, он работает ls
в базовой папке, а затем во всех дочерних папках. И все внуковые папки, до бесконечности. По сути, команда рекурсивно просматривает каждую папку, пока не достигнет конца дерева каталогов. В этот момент он возвращает ветку в дереве и делает то же самое для любых подпапок, если они есть.
Или в псевдокоде:
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
}
}
}
И потому что я могу, эталонная реализация Java того же самого.
По сути, вы можете задать два тесно связанных вопроса.
ls
? Из вашей фразы («Как рекурсия используется в процессе?») Я думаю, что это часть того, что вы хотите знать. Этот ответ отвечает на этот вопрос.ls
быть реализованным с помощью рекурсивной техники:FOLDOC определяет рекурсию как:
Когда функция (или процедура ) вызывает себя. Такая функция называется «рекурсивная». Если вызов осуществляется с помощью одной или нескольких других функций, эта группа функций называется «взаимно рекурсивной».
Естественным способом реализации ls
является написание функции, которая создает список записей файловой системы для отображения и другой код для обработки аргументов пути и параметров и для отображения записей по желанию. Скорее всего, эта функция будет реализована рекурсивно.
Во время обработки опции ls
определит, было ли ей предложено работать рекурсивно (вызывается с -R
флагом). Если это так, функция, которая создает список записей для отображения, будет вызывать себя один раз для каждого каталога, который она перечисляет, кроме .
и ..
. Там могут быть отдельные рекурсивные и нерекурсивные версии этой функции, или функция может проверять каждый раз, если она должна работать рекурсивно.
Ubuntu /bin/ls
, исполняемый файл, который запускается при запуске ls
, предоставляется GNU Coreutils и имеет много функций. В результате его код несколько длиннее и сложнее, чем вы могли ожидать. Но Ubuntu также содержит более простую версию ls
, предоставленную BusyBox . Вы можете запустить это, набрав busybox ls
.
busybox ls
используется рекурсия:ls
в BusyBox реализован в coreutils/ls.c
. Он содержит scan_and_display_dirs_recur()
функцию, которая вызывается для рекурсивной печати дерева каталогов:
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);
}
}
}
Строка, где происходит рекурсивный вызов функции:
scan_and_display_dirs_recur(dnd, 0);
Вы можете увидеть это в действии, если вы запускаете busybox ls
в отладчике. Сначала установите символы отладки , включив пакеты -dbgsym.ddeb, а затем установите busybox-static-dbgsym
пакет. Установите gdb
также (это отладчик).
sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym
Я предлагаю отладку coreutils ls
на простом дереве каталогов.
Если у вас нет одного удобного, сделайте его (это работает так же, как mkdir -p
команда в ответе WinEunuuchs2Unix ):
mkdir -pv foo/{bar/foobar,baz/quux}
И заполните его некоторыми файлами:
(shopt -s globstar; for d in foo/**; do touch "$d/file$((i++))"; done)
Вы можете проверить busybox ls -R foo
работоспособность, как и ожидалось, выдав такой вывод:
foo:
bar baz file0
foo/bar:
file1 foobar
foo/bar/foobar:
file2
foo/baz:
file3 quux
foo/baz/quux:
file4
Откройте busybox
в отладчике:
gdb busybox
GDB напечатает некоторую информацию о себе. Тогда это должно сказать что-то вроде:
Reading symbols from busybox...Reading symbols from /usr/lib/debug/.build-id/5c/e436575b628a8f54c2a346cc6e758d494c33fe.debug...done.
done.
(gdb)
(gdb)
это ваша подсказка в отладчике. Первое, что вы скажете GDB сделать в этом приглашении, это установить точку останова в начале scan_and_display_dirs_recur()
функции:
b scan_and_display_dirs_recur
Когда вы запускаете это, GDB должен сказать вам что-то вроде:
Breakpoint 1 at 0x5545b4: file coreutils/ls.c, line 1026.
Теперь скажите GDB работать busybox
с (или любым другим именем каталога) в качестве аргументов:ls -R foo
run ls -R foo
Вы можете увидеть что-то вроде этого:
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.
Если вы видите No such file or directory
, как указано выше, это нормально. Цель этой демонстрации - просто посмотреть, когда scan_and_display_dirs_recur()
была вызвана функция, поэтому GDB не нужно проверять фактический исходный код.
Обратите внимание, что отладчик достиг точки останова еще до того, как были напечатаны какие-либо записи каталога. Это нарушает вход в эту функцию, но код в этой функции должен выполняться для любых каталогов, перечисляемых для печати.
Чтобы сказать GDB продолжить, запустите:
c
Каждый раз, когда scan_and_display_dirs_recur()
вызывается, точка останова будет снова нажата, так что вы увидите рекурсию в действии. Это выглядит так (включая (gdb)
подсказку и ваши команды):
(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]
Функция имеет recur
свое имя ... BusyBox использует ее только при -R
заданном флаге? В отладчике это легко узнать:
(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]
Даже без -R
, эта конкретная реализация ls
использует ту же функцию, чтобы выяснить, какие записи файловой системы существуют и показать их.
Если вы хотите выйти из отладчика, просто скажите это:
q
scan_and_display_dirs_recur()
узнать, должен ли он называть себя:В частности, как это работает по-другому, когда -R
флаг передается? Изучение исходного кода (который может быть не точной версией в вашей системе Ubuntu) показывает, что он проверяет свою внутреннюю структуру данных G.all_fmt
, где хранит параметры, с которыми он был вызван:
if (ENABLE_FEATURE_LS_RECURSIVE
&& (G.all_fmt & DISP_RECURSIVE)
(Если BusyBox был скомпилирован без поддержки -R
, то он также не будет пытаться рекурсивно отображать записи файловой системы; в этом суть ENABLE_FEATURE_LS_RECURSIVE
части.)
Только когда G.all_fmt & DISP_RECURSIVE
true, код, содержащий рекурсивный вызов функции, запускается.
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);
}
В противном случае функция запускается только один раз (для каждого каталога, указанного в командной строке).
Когда вы думаете об этом, «рекурсивный» имеет смысл для команд, которые действуют на каталоги и их файлы и каталоги, а также их файлы и каталоги, их файлы и каталоги и их файлы .........
.... до тех пор, пока команда не оперирует всем деревом с указанной точки вниз, в этом случае перечисляется содержимое любых подкаталогов любых подкаталогов любых подкаталогов .........., которые существуют под аргумент (ы) команды
-R для рекурсии, которую можно было бы назвать «неоднократно».
Возьмите этот код для примера:
───────────────────────────────────────────────────────────────────────────────
$ 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:
Создание -p
каталогов позволяет массово создавать каталоги с помощью одной команды. Если один или несколько каталогов верхнего среднего уровня уже существуют, это не является ошибкой, и каталоги среднего нижнего уровня создаются.
Затем ls -R
рекурсивно перечисляется каждый каталог, начиная с temp и работая вниз по дереву до всех веток.
Теперь давайте посмотрим на дополнение к ls -R
команде, то есть tree
команду:
$ tree temp
temp
├── a
├── b
│ └── 1
└── c
└── 1
└── 2
6 directories, 0 files
Как вы видите, tree
выполняет то же, что и ls -R
за исключением, является более кратким, и я смею говорить «красивее».
Теперь давайте посмотрим, как рекурсивно удалять каталоги, которые мы только что создали, одной простой командой:
$ rm -r temp
Это рекурсивно удаляет temp
и все подкаталоги под ним. то есть temp/a
, temp/b/1
и temp/c/1/2
плюс средние каталоги между ними.
tree
хотя. Это отличный инструмент.
Вот простое объяснение, это имеет смысл, потому что когда дело доходит до отображения содержимого подкаталогов, та же самая функция уже знает, что делать с каталогом. Поэтому для получения такого результата просто нужно вызвать себя в каждом подкаталоге!
В псевдокоде это выглядит примерно так:
recursive_ls(dir)
print(files and directories)
foreach (directoriy in dir)
recursive_ls(directory)
end foreach
end recursive_ls