ls -R
はディレクトリのリストを表示することを理解しています。しかし、なぜ再帰的ですか?プロセスで再帰はどのように使用されますか?
まず、任意のフォルダー構造を定義しましょう。
.
├── 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
}
}
}
そして、私ができるので、同じの reference Java implementation 。
実際には、2つの密接に関連した質問があります。
ls
の実装では、再帰のテクニックはどのように使用されますか?あなたの言い回し(「プロセスでの再帰の使用方法」)から、これはあなたが知りたいことの一部だと思います。この答えはその質問に対処します。ls
を再帰的な手法で実装するのが理にかなっている理由:function (または procedure )がそれ自体を呼び出す場合。このような関数は「再帰的」と呼ばれます。呼び出しが1つ以上の他の関数を介して行われる場合、この関数グループは「相互再帰的」と呼ばれます。
ls
を実装する自然な方法は、表示するファイルシステムエントリのリストを作成する関数と、パスとオプションの引数を処理し、必要に応じてエントリを表示する他のコードを記述することです。その関数は再帰的に実装される可能性が高いです。
オプションの処理中、ls
は(-R
フラグで呼び出されることにより)再帰的に動作するように要求されたかどうかを判断します。その場合、表示するエントリのリストを作成する関数は、.
および..
を除き、リストするディレクトリごとに1回それ自体を呼び出します。この関数には再帰的バージョンと非再帰的バージョンが別々に存在する場合があります。または、関数は再帰的に動作することになっている場合は毎回チェックする場合があります。
Ubuntuの/bin/ls
は、ls
を実行したときに実行される実行可能ファイルであり、 GNU Coreutils によって提供され、many機能を備えています。その結果、 そのコード は予想よりもやや長く複雑です。 しかし、Ubuntuには BusyBox 。が提供するls
のよりシンプルなバージョンも含まれています。これは、busybox ls
と入力することで実行できます。
busybox ls
が再帰を使用する方法:BusyBoxのls
は、 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
をデバッグすることをお勧めします。
便利なものがない場合は、1つ作成します(これは WinEunuuchs2Unixの答え のmkdir -p
コマンドと同じように機能します)。
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に、ls -R foo
(または任意のディレクトリ名)を引数としてbusybox
を実行するように指示します。
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は実際のソースコードを調べる必要はありません。
ディレクトリエントリが出力される前でも、デバッガがブレークポイントにヒットすることに注意してください。その関数へのentraceで中断しますが、その関数内のコードは、印刷用に列挙されるディレクトリに対して実行する必要があります。
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)
(-R
のサポートなしでBusyBoxがコンパイルされている場合、ファイルシステムエントリを再帰的に表示しようとはしません。これが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);
}
それ以外の場合、関数は1回だけ実行されます(コマンドラインで指定されたディレクトリごとに)。
考えてみると、ディレクトリとそのファイルとディレクトリ、およびそのファイルとディレクトリ、およびそのファイルとディレクトリとそのファイルに作用するコマンドには「再帰的」という意味があります.........
....指定されたポイントから下のツリー全体がコマンドによって操作されるまで、この場合は、サブディレクトリのサブディレクトリのサブディレクトリの内容をリストします..........コマンドの引数
-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
を使用すると、1つのコマンドでディレクトリを大量に作成できます。 1つ以上の最上位中間ディレクトリが既に存在する場合、エラーではなく、中間下位ディレクトリが作成されます。
次に、ls -R
は、tempで始まり、ツリーを下ってすべてのブランチに移動するすべてのディレクトリを再帰的にリストします。
ls -R
コマンドの補完、つまりtree
コマンドを見てみましょう。
$ tree temp
temp
├── a
├── b
│ └── 1
└── c
└── 1
└── 2
6 directories, 0 files
ご覧のとおり、tree
はls -R
と同じことを達成しますが、より簡潔であえて「きれい」と言います。
ここで、作成したディレクトリを1つの簡単なコマンドで再帰的に削除する方法を見てみましょう。
$ rm -r temp
これにより、temp
とその下のすべてのサブディレクトリが再帰的に削除されます。すなわち、temp/a
、temp/b/1
、およびtemp/c/1/2
に加えて、中間の中間ディレクトリ。
ここに簡単な説明があります。サブディレクトリのコンテンツを表示することになると、同じ関数はすでにディレクトリをどうするかを知っているので、理にかなっています。したがって、その結果を得るには、各サブディレクトリで自分自身を呼び出すだけです!
擬似コードでは、次のようになります。
recursive_ls(dir)
print(files and directories)
foreach (directoriy in dir)
recursive_ls(directory)
end foreach
end recursive_ls