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
        }
    }
}

그리고 내가 할 수 있기 때문에 동일한 자바 구현참조 하십시오.


23

실제로 두 가지 밀접하게 관련된 질문이 있습니다.

  • 파일 시스템 계층의 각 항목으로 이동하는 프로세스가 본질적으로 재귀적인 프로세스 인 이유는 무엇입니까? 이것은 ZannaKaz Wolfe 와 같은 다른 답변으로 해결됩니다 .
  • 어떻게되는 기술 재귀는의 구현에 사용 ls? 당신의 어구 ( "프로세스에서 재귀는 어떻게 사용 되는가?")에서 나는 이것이 당신이 알고 싶어하는 것의 일부라고 생각합니다. 이 답변은 그 질문을 다룹니다.

ls재귀 기술로 구현 하는 것이 적합한 이유 :

FOLDOC재귀 를 다음과 같이 정의합니다 .

함수 (또는 프로 시저 )가 자신을 호출 할 때 이러한 기능을 "재귀"라고합니다. 호출이 하나 이상의 다른 기능을 통해 이루어지면이 기능 그룹을 "상호 재귀"라고합니다.

자연스럽게 구현하는 방법 ls은 표시 할 파일 시스템 항목 목록과 경로 및 옵션 인수를 처리하고 원하는대로 항목을 표시하는 다른 코드를 구성하는 함수를 작성하는 것입니다. 이 기능은 재귀 적으로 구현 될 가능성이 높습니다.

옵션 처리 중에 ls재귀 적으로 작동하도록 요청되었는지 ( -R플래그 로 호출하여 ) 결정합니다. 그렇다면 표시 할 항목 목록을 작성하는 함수는 및를 제외 .하고 나열된 각 디렉토리마다 한 번씩 호출됩니다 ... 이 함수에는 재귀 및 비 재귀 버전이 별도로 있거나 재귀 적으로 작동해야하는 경우 매번 검사 할 수 있습니다.

/bin/ls실행할 때 실행되는 실행 파일 인 Ubuntu 's lsGNU Coreutils 에서 제공하며 많은 기능이 있습니다. 결과적으로 코드 는 예상보다 다소 길고 복잡합니다. 그러나 우분투에는 BusyBoxls 에서 제공 하는 더 간단한 버전도 포함되어 있습니다 . 을 입력하여 실행할 수 있습니다 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-dbgsymgdb

sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym

coreutils ls간단한 디렉토리 트리에서 디버깅 하는 것이 좋습니다 .

편리한 것이 없다면 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는 실제 소스 코드를 검사 할 필요가 없습니다.

디렉토리 항목이 인쇄되기 전에 디버거가 중단 점에 도달했습니다. 해당 함수 의 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플래그가 전달 될 때 어떻게 다르게 작동 합니까? 심사 소스 코드 (우분투 시스템의 정확한 버전을하지 않을 수 있습니다) 그것의 내부 데이터 구조를 점검 것을 알 수 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.
Kaz Wolfe

2
아, 그래서 꼬리 재귀도 아닙니다. 이것은 스택 오버플로로 인해 busybox가 충돌하는 디렉토리 내용이 있음을 의미해야합니다 (매우 깊은 중첩 일지라도).
Ruslan

2
놀랍습니다. 기본적으로 OP에 디버깅에 대한 빠른 교훈을 제공하여 사물이 어떻게 작동하는지 정확하게 이해할 수 있도록합니다. 훌륭한.
Andrea Lazzarotto

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디렉토리를 만드는 당신이 대량 하나의 명령으로 디렉토리를 생성 할 수 있습니다. 최상위 중간 디렉토리 중 하나 이상이 이미 존재하는 경우 오류가 아니며 중간 하한 디렉토리가 작성됩니다.

그런 다음 ls -Rtemp로 시작하여 모든 단일 디렉토리를 재귀 적으로 나열하고 트리에서 모든 분기까지 작동합니다.

이제 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/1temp/c/1/2플러스 사이의 중간 디렉토리.


만약 "ls -R"이 반복해서 무언가 를한다면 같은 출력을 여러 번 얻을 수 tree있지만 ; 1입니다 . 훌륭한 도구입니다.
포드

예, 평신도의 말을 잘 못합니다. 프로그래머가 아닌 유형을 쉽게 이해할 수 있도록 주류에서 단어를 찾으려고했습니다. 더 나은 단어를 생각하거나 나중에 삭제하려고합니다.
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.