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
}
}
}
実際には、2つの密接に関連した質問があります。
ls
ますか?あなたの言い回し(「プロセスでの再帰の使用方法」)から、これはあなたが知りたいことの一部だと思います。この答えはその質問に対処します。ls
再帰的な手法で実装するのが理にかなっている理由:関数(またはプロシージャー)がそれ自体を呼び出すとき。このような関数は「再帰的」と呼ばれます。呼び出しが他の1つまたは複数の関数を経由する場合、この関数グループは「相互再帰的」と呼ばれます。
実装する自然な方法ls
は、表示するファイルシステムエントリのリストを作成する関数、およびパスとオプションの引数を処理し、必要に応じてエントリを表示する他のコードを記述することです。その関数は再帰的に実装される可能性が高いです。
オプションの処理中に、ls
再帰的に動作するように要求されたかどうかを判断します(-R
フラグを指定して呼び出されます)。その場合、表示するエントリのリストを作成する関数は、とを除く.
、リストするディレクトリごとに1回それ自体を呼び出します..
。この関数には再帰バージョンと非再帰バージョンが別々に存在する場合があります。または、関数は再帰的に動作することになっている場合は毎回チェックする場合があります。
Ubuntuの/bin/ls
実行時に実行される実行可能ファイルls
はGNU Coreutilsによって提供され、多くの機能を備えています。その結果、コードは予想よりも多少長く複雑になります。ただし、UbuntuにはBusyBoxがls
提供するのよりシンプルなバージョンも含まれています。これを実行するには、と入力します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
単純なディレクトリツリーでデバッグすることをお勧めします。
便利なものがない場合は、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に(または任意のディレクトリ名)を引数として実行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);
}
それ以外の場合、関数は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つ以上の最上位中間ディレクトリが既に存在する場合、エラーではなく、中間下位ディレクトリが作成されます。
次に、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
中間の中間ディレクトリ。
tree
かかわらず。それは素晴らしいツールです。