Я розумію, що 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
в базовій папці, а потім у всіх дочірніх папках. І всі папки онука, ad infinitum. Ефективно команда проходить через кожну папку рекурсивно, поки не потрапить у кінець дерева каталогів. У цей момент він повертається до гілки дерева і робить те саме для будь-яких підпапок, якщо такі є.
Або в псевдокоді:
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
реалізувати за допомогою рекурсивної техніки:Коли функція (або процедура ) викликає себе. Така функція називається "рекурсивною". Якщо виклик здійснюється через одну або декілька інших функцій, то ця група функцій називається "взаємно рекурсивною".
Природним способом реалізації ls
є написання функції, яка будує список записів файлової системи, що відображається, та інший код для обробки аргументів шляху та параметрів та відображення записів за бажанням. Ця функція з великою ймовірністю може бути реалізована рекурсивно.
Під час обробки опції ls
визначить, чи було запропоновано діяти рекурсивно (викликаючи -R
прапор). Якщо це так, то функція , яка створює список записів , які будуть відображатися буде називати себе один раз для кожного каталогу , вона перераховує, за винятком .
і ..
. Можуть існувати окремі рекурсивні та нерекурсивні версії цієї функції, або функція може перевіряти кожен раз, чи передбачається, що вона працює рекурсивно.
Ubuntu's /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
це правда, запускається код, що містить рекурсивний виклик функції.
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