ls -Rが「再帰的」リストと呼ばれるのはなぜですか?


36

ls -Rディレクトリのリストが表示されることを理解しています。しかし、なぜ再帰的ですか?プロセスで再帰はどのように使用されますか?


12
直観は、ディレクトリとそのサブディレクトリをツリーを使用して簡単にモデル化できるということです。通常、木を歩くアルゴリズムは再帰的です。
ケビン-モニカーの復活

1
@Kevin各質問に答えるためにツリーの概念を呼び出す必要はないと思います-答えはls、ディレクトリに遭遇すると、そのディレクトリを再帰的にリストするということです。
user253751

回答:


67

まず、任意のフォルダー構造を定義しましょう。

.
├── 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実装参照します


23

実際には、2つの密接に関連した質問があります。

  • ファイルシステム階層の各エントリに移動するプロセスが、本質的に再帰的なプロセスであるのはなぜですか?これは、ZannaKaz Wolfeなどの他の回答で対処されています。
  • の実装で再帰の手法はどのように使用されlsますか?あなたの言い回し(「プロセスでの再帰の使用方法」)から、これはあなたが知りたいことの一部だと思います。この答えはその質問に対処します。

ls再帰的な手法で実装するのが理にかなっている理由:

FOLDOCは、再帰を次のように定義します

関数(またはプロシージャー)がそれ自体を呼び出すとき。このような関数は「再帰的」と呼ばれます。呼び出しが他の1つまたは複数の関数を経由する場合、この関数グループは「相互再帰的」と呼ばれます。

実装する自然な方法lsは、表示するファイルシステムエントリのリストを作成する関数、およびパスとオプションの引数を処理し、必要に応じてエントリを表示する他のコードを記述することです。その関数は再帰的に実装される可能性が高いです。

オプションの処理中に、ls再帰的に動作するように要求されたかどうかを判断します(-Rフラグを指定して呼び出されます)。その場合、表示するエントリのリストを作成する関数は、とを除く.、リストするディレクトリごとに1回それ自体を呼び出します..。この関数には再帰バージョンと非再帰バージョンが別々に存在する場合があります。または、関数は再帰的に動作することになっている場合は毎回チェックする場合があります。

Ubuntuの/bin/ls実行時に実行される実行可能ファイルlsGNU Coreutilsによって提供され、多くの機能を備えてます。その結果、コードは予想よりも多少長く複雑になります。ただし、UbuntuにはBusyBoxがls提供するのよりシンプルなバージョンも含まれています。これを実行するには、と入力しますbusybox ls

busybox ls再帰の使用方法:

lsBusyBoxで実装されてい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_RECURSIVEtrueの場合のみ、再帰的な関数呼び出しを含むコードが実行されます。

                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回だけ実行されます(コマンドラインで指定されたディレクトリごとに)。


もう一度、エリアは非常に包括的な答えを出します。当然の+1。
カズウルフ

2
ああ、だからテール再帰すらありません。これは、いくつかのディレクトリコンテンツが存在することを意味する必要があります。リストは、スタックオーバーフローのためにbusyboxをクラッシュさせます(ただし、非常に深い入れ子になります)。
ルスラン

2
これは驚くべきことです。基本的に、OPにデバッグの簡単なレッスンを提供して、OPがどのように機能しているかを正確に理解できるようにしました。見事。
アンドレア

16

考えてみると、ディレクトリとそのファイルとディレクトリ、およびそのファイルとディレクトリ、およびそのファイルとディレクトリとそのファイルに作用するコマンドには「再帰的」という意味があります.........

....指定されたポイントからツリー全体がコマンドによって操作されるまで、この場合は、サブディレクトリのサブディレクトリのサブディレクトリの内容を一覧表示するまで..........コマンドの引数


7

-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 -Rtempで始まるすべてのディレクトリを再帰的にリストし、ツリーを下ってすべてのブランチに移動します。

ここで、ls -Rコマンドの補完、つまりコマンドを見てみましょうtree

$ tree temp
temp
├── a
├── b
│   └── 1
└── c
    └── 1
        └── 2

6 directories, 0 files

ご覧のとおりtreels -Rより簡潔で敢えて「きれい」と言う以外は同じです。

次に、作成したディレクトリを1つの簡単なコマンドで再帰的に削除する方法を見てみましょう。

$ rm -r temp

これによりtemp、その下のすべてのサブディレクトリが再帰的に削除されます。すなわちtemp/atemp/b/1およびtemp/c/1/2中間の中間ディレクトリ。


「LSは-R」何かをした場合は、繰り返し、あなたは同じ出力を複数回取得したい;)+1のためにtreeかかわらず。それは素晴らしいツールです。
ポッド

素人の言葉の悪い声。私は、主流の言葉を見つけて、プログラマーでない人でも理解しやすくしようとしていました。より良い単語を考えたり、後で削除したりします。
WinEunuuchs2Unix

5

以下に簡単な説明を示します。サブディレクトリのコンテンツを表示する場合、同じ関数がディレクトリをどう処理するかをすでに知っているため、それは理にかなっています。したがって、その結果を得るには、各サブディレクトリで自分自身を呼び出すだけです!

擬似コードでは、次のようになります。

recursive_ls(dir)
    print(files and directories)
    foreach (directoriy in dir)
        recursive_ls(directory)
    end foreach
end recursive_ls
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.