Tôi hiểu rằng ls -R
hiển thị một danh sách các thư mục. Nhưng tại sao nó lại đệ quy? Làm thế nào là đệ quy được sử dụng trong quá trình?
ls
gặp một thư mục, nó sẽ liệt kê đệ quy thư mục đó.
Tôi hiểu rằng ls -R
hiển thị một danh sách các thư mục. Nhưng tại sao nó lại đệ quy? Làm thế nào là đệ quy được sử dụng trong quá trình?
ls
gặp một thư mục, nó sẽ liệt kê đệ quy thư mục đó.
Câu trả lời:
Trước hết, hãy xác định cấu trúc thư mục tùy ý:
.
├── 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]
Khi chúng tôi làm ls
, chúng tôi chỉ nhận được đầu ra của thư mục cơ sở:
a1 a2 a3 a4
Tuy nhiên, khi chúng tôi gọi ls -R
, chúng tôi nhận được một cái gì đó khác nhau:
.:
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:
Như bạn có thể thấy, nó đang chạy ls
trên thư mục cơ sở, và sau đó là tất cả các thư mục con. Và tất cả các thư mục cháu, ad infinitum. Thực tế, lệnh sẽ đi qua từng thư mục một cách đệ quy cho đến khi nó chạm vào cuối cây thư mục. Tại thời điểm đó, nó quay trở lại một nhánh trong cây và thực hiện điều tương tự cho bất kỳ thư mục con nào, nếu có.
Hoặc, trong mã giả:
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
}
}
}
Và bởi vì tôi có thể, một triển khai Java tham chiếu giống nhau.
Trên thực tế, có hai câu hỏi được kết hợp chặt chẽ mà bạn có thể hỏi.
ls
? Từ cụm từ của bạn ("đệ quy được sử dụng như thế nào trong quy trình?"), Tôi nghĩ đây là một phần của những gì bạn muốn biết. Câu trả lời này giải quyết câu hỏi đó.ls
để được thực hiện với một kỹ thuật đệ quy:Khi một chức năng (hoặc thủ tục ) gọi chính nó. Một chức năng như vậy được gọi là "đệ quy". Nếu cuộc gọi thông qua một hoặc nhiều chức năng khác thì nhóm chức năng này được gọi là "đệ quy lẫn nhau".
Cách tự nhiên để thực hiện ls
là viết một hàm xây dựng danh sách các mục hệ thống tệp sẽ được hiển thị và mã khác để xử lý các đối số đường dẫn và tùy chọn và hiển thị các mục theo mong muốn. Chức năng đó rất có thể được thực hiện đệ quy.
Trong quá trình xử lý tùy chọn, ls
sẽ xác định xem nó có được yêu cầu hoạt động đệ quy không (bằng cách được gọi bằng -R
cờ). Nếu vậy, hàm xây dựng danh sách các mục sẽ được hiển thị sẽ tự gọi một lần cho mỗi thư mục mà nó liệt kê, ngoại trừ .
và ..
. Có thể có các phiên bản đệ quy và không đệ quy riêng biệt của chức năng này hoặc chức năng có thể kiểm tra mỗi lần nếu nó được cho là hoạt động đệ quy.
Ubuntu /bin/ls
, chương trình thực thi chạy khi bạn chạy ls
, được cung cấp bởi GNU Coreutils và nó có nhiều tính năng. Do đó, mã của nó có phần dài hơn và phức tạp hơn bạn mong đợi. Nhưng Ubuntu cũng chứa một phiên bản đơn giản hơn ls
, được cung cấp bởi BusyBox . Bạn có thể chạy nó bằng cách gõ busybox ls
.
busybox ls
sử dụng đệ quy:ls
trong BusyBox được triển khai trong coreutils/ls.c
. Nó chứa một scan_and_display_dirs_recur()
hàm được gọi để in một cây thư mục theo cách đệ quy:
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);
}
}
}
Dòng nơi diễn ra cuộc gọi hàm đệ quy là:
scan_and_display_dirs_recur(dnd, 0);
Bạn có thể thấy điều này trong hoạt động nếu bạn chạy busybox ls
trong trình gỡ lỗi. Đầu tiên cài đặt các ký hiệu gỡ lỗi bằng cách kích hoạt các gói -dbgsym.ddeb và sau đó cài đặt busybox-static-dbgsym
gói. Cài đặt gdb
là tốt (đó là trình gỡ lỗi).
sudo apt-get update
sudo apt-get install gdb busybox-static-dbgsym
Tôi đề nghị gỡ lỗi coreutils ls
trên một cây thư mục đơn giản.
Nếu bạn không có tiện ích, hãy tạo một cái (cách này hoạt động giống như mkdir -p
lệnh trong câu trả lời của WinEunuuchs2Unix ):
mkdir -pv foo/{bar/foobar,baz/quux}
Và điền vào nó một số tệp:
(shopt -s globstar; for d in foo/**; do touch "$d/file$((i++))"; done)
Bạn có thể xác minh busybox ls -R foo
công việc như mong đợi, tạo ra đầu ra này:
foo:
bar baz file0
foo/bar:
file1 foobar
foo/bar/foobar:
file2
foo/baz:
file3 quux
foo/baz/quux:
file4
Mở busybox
trong trình gỡ lỗi:
gdb busybox
GDB sẽ in một số thông tin về chính nó. Sau đó, nó sẽ nói một cái gì đó như:
Reading symbols from busybox...Reading symbols from /usr/lib/debug/.build-id/5c/e436575b628a8f54c2a346cc6e758d494c33fe.debug...done.
done.
(gdb)
(gdb)
là lời nhắc của bạn trong trình gỡ lỗi. Điều đầu tiên bạn sẽ bảo GDB thực hiện trên lời nhắc này là đặt điểm dừng khi bắt đầu scan_and_display_dirs_recur()
chức năng:
b scan_and_display_dirs_recur
Khi bạn chạy nó, GDB sẽ cho bạn biết một cái gì đó như:
Breakpoint 1 at 0x5545b4: file coreutils/ls.c, line 1026.
Bây giờ hãy nói với GDB để chạy busybox
với (hoặc bất kỳ tên thư mục nào bạn muốn) làm đối số của nó:ls -R foo
run ls -R foo
Bạn có thể thấy một cái gì đó như thế này:
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.
Nếu bạn thấy No such file or directory
, như trên, thì không sao. Mục đích của trình diễn này là để xem khi nào scan_and_display_dirs_recur()
hàm được gọi, vì vậy GDB không cần kiểm tra mã nguồn thực tế.
Lưu ý rằng trình gỡ lỗi đã chạm đến điểm dừng ngay cả trước khi bất kỳ mục nhập thư mục nào được in. Nó phá vỡ trên entrace để chức năng đó, nhưng các mã trong chức năng đó phải chạy cho bất kỳ thư mục được liệt kê để in.
Để báo cho GDB tiếp tục, hãy chạy:
c
Mỗi lần scan_and_display_dirs_recur()
được gọi, điểm dừng sẽ được nhấn lại, vì vậy bạn sẽ thấy đệ quy trong hành động. Nó trông như thế này (bao gồm (gdb)
lời nhắc và các lệnh của bạn):
(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]
Hàm có recur
tên của nó ... BusyBox chỉ sử dụng nó khi -R
cờ được đưa ra? Trong trình gỡ lỗi, điều này rất dễ tìm ra:
(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]
Ngay cả khi không có -R
, việc triển khai cụ thể này ls
sử dụng cùng một chức năng để tìm ra những mục hệ thống tập tin tồn tại và hiển thị chúng.
Khi bạn muốn thoát khỏi trình gỡ lỗi, chỉ cần nói với nó:
q
scan_and_display_dirs_recur()
biết nếu nó nên gọi chính nó:Cụ thể nó hoạt động khác nhau như thế nào khi -R
cờ được thông qua? Kiểm tra mã nguồn (có thể không phải là phiên bản chính xác trên hệ thống Ubuntu của bạn) cho thấy rằng nó kiểm tra cấu trúc dữ liệu bên trong của nó G.all_fmt
, nơi nó lưu trữ các tùy chọn mà nó đã được gọi với:
if (ENABLE_FEATURE_LS_RECURSIVE
&& (G.all_fmt & DISP_RECURSIVE)
(Nếu BusyBox đã được biên dịch mà không có hỗ trợ -R
, thì nó cũng sẽ không cố gắng hiển thị các mục hệ thống tệp theo cách đệ quy; đó là những gì ENABLE_FEATURE_LS_RECURSIVE
phần đó nói về.)
Chỉ khi nào G.all_fmt & DISP_RECURSIVE
đúng thì mã chứa hàm gọi đệ quy mới được chạy.
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);
}
Mặt khác, hàm chỉ chạy một lần (mỗi thư mục được chỉ định trên dòng lệnh).
Khi bạn nghĩ về nó, "đệ quy" có ý nghĩa đối với các lệnh hoạt động trên các thư mục và các tệp và thư mục của chúng cũng như các tệp và thư mục của chúng cũng như các tệp và thư mục cũng như các tệp của chúng .........
.... cho đến khi toàn bộ cây từ điểm được chỉ định xuống được vận hành bằng lệnh, trong trường hợp này liệt kê nội dung của bất kỳ thư mục con nào của bất kỳ thư mục con nào của bất kỳ thư mục con nào .......... tồn tại dưới đối số của lệnh
-R là để đệ quy, có thể được gọi là "lặp đi lặp lại".
Lấy mã này làm ví dụ:
───────────────────────────────────────────────────────────────────────────────
$ 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:
Việc -p
tạo các thư mục cho phép bạn tạo các thư mục hàng loạt bằng một lệnh duy nhất. Nếu một hoặc nhiều thư mục ở giữa đã tồn tại thì đó không phải là lỗi và các thư mục ở giữa thấp hơn được tạo.
Sau đó, ls -R
đệ quy liệt kê tất cả các thư mục duy nhất bắt đầu bằng temp và làm việc theo cách của nó xuống cây cho tất cả các nhánh.
Bây giờ chúng ta hãy xem xét một bổ sung cho ls -R
lệnh, tức là tree
lệnh:
$ tree temp
temp
├── a
├── b
│ └── 1
└── c
└── 1
└── 2
6 directories, 0 files
Như bạn có thể thấy tree
những thành tựu tương tự như ls -R
ngoại trừ ngắn gọn hơn và tôi dám nói "xinh hơn".
Bây giờ hãy xem cách loại bỏ đệ quy các thư mục chúng ta vừa tạo trong một lệnh đơn giản:
$ rm -r temp
Điều này đệ quy loại bỏ temp
và tất cả các thư mục con bên dưới nó. tức là temp/a
, temp/b/1
và temp/c/1/2
cộng với các thư mục ở giữa.
tree
. Đó là một công cụ tuyệt vời.
Đây là một lời giải thích đơn giản, nó có ý nghĩa bởi vì khi hiển thị nội dung của các thư mục con, cùng một chức năng đã biết phải làm gì với một thư mục. Vì vậy, nó chỉ cần gọi chính nó trên mỗi thư mục con để có được kết quả đó!
Trong mã giả, nó trông giống như thế này:
recursive_ls(dir)
print(files and directories)
foreach (directoriy in dir)
recursive_ls(directory)
end foreach
end recursive_ls