ฉันเข้าใจว่า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
}
}
}
และเพราะฉันสามารถอ้างอิง Java ใช้งานเดียวกัน
มีคำถามสองข้อที่คุณอาจถามอย่างใกล้ชิด
ls
อย่างไร จากคำพูดของคุณ ("การเรียกซ้ำใช้ในกระบวนการอย่างไร") ฉันคิดว่านี่เป็นส่วนหนึ่งของสิ่งที่คุณอยากรู้ คำตอบนี้ตอบคำถามนั้นls
จะนำไปใช้กับเทคนิคแบบเรียกซ้ำ:เมื่อฟังก์ชั่น (หรือขั้นตอน ) เรียกตัวเอง ฟังก์ชั่นดังกล่าวเรียกว่า "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);
}
มิฉะนั้นฟังก์ชันจะทำงานเพียงครั้งเดียว (ต่อไดเรกทอรีที่ระบุในบรรทัดคำสั่ง)
เมื่อคุณคิดถึงมัน "recursive" จะเหมาะสมสำหรับคำสั่งที่ทำหน้าที่กับไดเรกทอรีและไฟล์และไดเรกทอรีรวมถึงไฟล์และไดเรกทอรีรวมถึงไฟล์และไดเรกทอรีและไฟล์ของพวกเขา .........
.... จนกว่าต้นไม้ทั้งหมดจากจุดที่ระบุลงได้ถูกดำเนินการโดยคำสั่งในกรณีนี้จะแสดงเนื้อหาของไดเรกทอรีย่อยใด ๆ ของไดเรกทอรีย่อยใด ๆ ของไดเรกทอรีย่อย .......... ที่มีอยู่ภายใต้ อาร์กิวเมนต์ของคำสั่ง
-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
บวกไดเรกทอรีกลางระหว่าง
tree
แม้ว่า มันเป็นเครื่องมือที่ยอดเยี่ยม
นี่เป็นคำอธิบายที่เข้าใจง่ายเพราะเมื่อมันมาถึงการแสดงเนื้อหาของไดเรกทอรีย่อยฟังก์ชั่นเดียวกันรู้อยู่แล้วว่าจะทำอย่างไรกับไดเรกทอรี ดังนั้นจึงจำเป็นต้องเรียกตัวเองในแต่ละไดเรกทอรีย่อยเพื่อรับผลนั้น!
ใน pseudocode ดูเหมือนว่า:
recursive_ls(dir)
print(files and directories)
foreach (directoriy in dir)
recursive_ls(directory)
end foreach
end recursive_ls