เหตุใด ls -R จึงเรียกว่ารายชื่อ“ recursive”


36

ฉันเข้าใจว่าls -Rแสดงรายชื่อของไดเรกทอรี แต่ทำไมถึงเกิดซ้ำ การเรียกซ้ำใช้ในกระบวนการอย่างไร


12
สัญชาตญาณคือไดเรกทอรีและไดเรกทอรีย่อยของพวกเขาสามารถสร้างแบบจำลองได้อย่างง่ายดายโดยใช้ต้นไม้ อัลกอริทึมในการเดินต้นไม้โดยทั่วไปจะเรียกซ้ำ
Kevin - Reinstate Monica

1
@ เควินฉันไม่คิดว่ามีความต้องการที่จะเรียกใช้แนวคิดของต้นไม้ที่จะตอบคำถามแต่ละคำถาม - คำตอบก็คือว่าเมื่อ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

มีคำถามสองข้อที่คุณอาจถามอย่างใกล้ชิด

  • ทำไมกระบวนการของการเดินไปยังแต่ละรายการในลำดับชั้นของระบบไฟล์เป็นกระบวนการวนซ้ำโดยเนื้อแท้? นี้ถูกแก้ไขโดยคำตอบอื่น ๆ เช่นZanna ของและKaz ของวูล์ฟ
  • มีการใช้เทคนิคการเรียกซ้ำในการดำเนินการlsอย่างไร จากคำพูดของคุณ ("การเรียกซ้ำใช้ในกระบวนการอย่างไร") ฉันคิดว่านี่เป็นส่วนหนึ่งของสิ่งที่คุณอยากรู้ คำตอบนี้ตอบคำถามนั้น

เหตุใดจึงเหมาะสมที่lsจะนำไปใช้กับเทคนิคแบบเรียกซ้ำ:

FOLDOCกำหนดการเรียกซ้ำเป็น:

เมื่อฟังก์ชั่น (หรือขั้นตอน ) เรียกตัวเอง ฟังก์ชั่นดังกล่าวเรียกว่า "recursive" หากการโทรผ่านฟังก์ชั่นอื่น ๆ หนึ่งฟังก์ชั่นกลุ่มนี้เรียกว่า "การเรียกซ้ำร่วมกัน"

วิธีที่เป็นธรรมชาติที่จะใช้lsคือการเขียนฟังก์ชั่นที่สร้างรายการของระบบไฟล์ที่จะแสดงและรหัสอื่น ๆ ในการประมวลผลเส้นทางและตัวเลือกข้อโต้แย้งและเพื่อแสดงรายการตามที่ต้องการ ฟังก์ชั่นนั้นมีโอกาสสูงที่จะถูกนำไปใช้ซ้ำ

ระหว่างการประมวลผลตัวเลือกlsจะพิจารณาว่าได้รับแจ้งให้ดำเนินการซ้ำ (โดยเรียกใช้กับ-Rแฟล็ก) ถ้าเป็นเช่นนั้นฟังก์ชั่นที่สร้างรายการของรายการที่จะแสดงจะเรียกตัวเองครั้งเดียวสำหรับแต่ละ directory มันรายการยกเว้นและ. ..อาจมีรุ่นที่เรียกซ้ำและแยกกันไม่ได้ของฟังก์ชันนี้หรือฟังก์ชั่นอาจตรวจสอบแต่ละครั้งถ้ามันควรจะทำงานซ้ำ

อูบุนตูเป็น/bin/lsปฏิบัติการที่วิ่งเมื่อคุณเรียกใช้lsเป็นที่จัดไว้ให้โดยGNU coreutilsและจะมีจำนวนมากคุณสมบัติ เป็นผลให้รหัสของมันค่อนข้างยาวและซับซ้อนกว่าที่คุณคาดไว้ แต่อูบุนตูนอกจากนี้ยังมีรุ่นที่เรียบง่ายของ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 จึงไม่จำเป็นต้องตรวจสอบซอร์สโค้ดจริง

ขอให้สังเกตว่าดีบักเกอร์กดเบรกพอยต์ก่อนที่จะพิมพ์รายการไดเรกทอรีใด ๆ มันแบ่งบน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)

(ถ้า 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);
                }

มิฉะนั้นฟังก์ชันจะทำงานเพียงครั้งเดียว (ต่อไดเรกทอรีที่ระบุในบรรทัดคำสั่ง)


อีกครั้งที่ Eliah มาพร้อมกับคำตอบที่ครอบคลุมมากเกินไป +1 ที่สมควรได้รับ
Kaz Wolfe

2
โอ้มันไม่ได้เรียกซ้ำแบบหางเลย นี่ต้องหมายความว่ามีเนื้อหาไดเรกทอรีอยู่บ้างการแสดงรายการซึ่งจะทำให้ busybox ไม่ทำงานเนื่องจากการล้นสแต็ก
Ruslan

2
นี่คือสิ่งที่น่าประหลาดใจ โดยพื้นฐานแล้วคุณได้ให้บทเรียนอย่างรวดเร็วเกี่ยวกับการดีบักเพื่อให้พวกเขาสามารถเข้าใจได้อย่างชัดเจนว่ามันทำงานอย่างไร ดีเยี่ยม
Andrea Lazzarotto

16

เมื่อคุณคิดถึงมัน "recursive" จะเหมาะสมสำหรับคำสั่งที่ทำหน้าที่กับไดเรกทอรีและไฟล์และไดเรกทอรีรวมถึงไฟล์และไดเรกทอรีรวมถึงไฟล์และไดเรกทอรีและไฟล์ของพวกเขา .........

.... จนกว่าต้นไม้ทั้งหมดจากจุดที่ระบุลงได้ถูกดำเนินการโดยคำสั่งในกรณีนี้จะแสดงเนื้อหาของไดเรกทอรีย่อยใด ๆ ของไดเรกทอรีย่อยใด ๆ ของไดเรกทอรีย่อย .......... ที่มีอยู่ภายใต้ อาร์กิวเมนต์ของคำสั่ง


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 -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บวกไดเรกทอรีกลางระหว่าง


หาก ls "-R" จะทำอะไรบางอย่างซ้ำ ๆแล้วคุณจะได้รับการส่งออกเดียวกันหลายครั้ง;) +1 สำหรับtreeแม้ว่า มันเป็นเครื่องมือที่ยอดเยี่ยม
พ็อด

ใช่เสียงที่น่าสงสารคำพูดของคนธรรมดา ฉันพยายามหาคำในกระแสหลักทำให้ง่ายขึ้นสำหรับผู้ที่ไม่ใช่โปรแกรมเมอร์ที่จะเข้าใจ ฉันจะพยายามคิดคำที่ดีขึ้นหรือลบในภายหลัง
WinEunuuchs2Unix

5

นี่เป็นคำอธิบายที่เข้าใจง่ายเพราะเมื่อมันมาถึงการแสดงเนื้อหาของไดเรกทอรีย่อยฟังก์ชั่นเดียวกันรู้อยู่แล้วว่าจะทำอย่างไรกับไดเรกทอรี ดังนั้นจึงจำเป็นต้องเรียกตัวเองในแต่ละไดเรกทอรีย่อยเพื่อรับผลนั้น!

ใน pseudocode ดูเหมือนว่า:

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.