Linux系統命令“ls -R”的實現
一、基本概念
1、“ls -R”的意義
遞歸處理,將指定目錄下的所有文件及子目錄一並顯示。
例: ls -R ./testdir1/
./testdir1/:
test1.c test1.txt test2.txt testdir2
./testdir1/testdir2:
test2.c test3.c test3.txt 到 test1.c 的鏈接
其中藍色為目錄文件,紅色為軟連接文件(具體顏色和vimrc配置有關)。
二、重要函數與結構體
1、目錄操作函數
這里的 void rewinddir(DIR *dirp); 函數非常重要,在讀取一遍目錄后,如果遺漏rewinddir函數會導致指向文件的指針停留在該目錄文件流的末尾,從而影響之后二次讀取。
1 #include <sys/types.h> 2 #include <dirent.h> 3 4 DIR *opendir(const char *name); 5 DIR *fdopendir(int fd); 6 7 8 #include <dirent.h> 9 10 struct dirent *readdir(DIR *dirp); 11 12 struct dirent { 13 ino_t d_ino; /* inode number */ 14 off_t d_off; /* offset to the next dirent */ 15 unsigned short d_reclen; /* length of this record */ 16 unsigned char d_type; /* type of file; not supported by all file system types */ 17 char d_name[256]; /* filename */ 18 };
1 #include <sys/types.h>
2 #include <dirent.h>
3
4 void rewinddir(DIR *dirp);
2、獲取文件信息
這里必須使用 int lstat(const char *path, struct stat *buf); 函數,否則在處理鏈接文件時會將其鏈接的原文件作為處理對象,而不是其本身。
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <unistd.h>
4
5 int stat(const char *path, struct stat *buf);
6 int fstat(int fd, struct stat *buf); 7 int lstat(const char *path, struct stat *buf); 8 9 struct stat { 10 dev_t st_dev; /* ID of device containing file */ 11 ino_t st_ino; /* inode number */ 12 mode_t st_mode; /* protection */ 13 nlink_t st_nlink; /* number of hard links */ 14 uid_t st_uid; /* user ID of owner */ 15 gid_t st_gid; /* group ID of owner */ 16 dev_t st_rdev; /* device ID (if special file) */ 17 off_t st_size; /* total size, in bytes */ 18 blksize_t st_blksize; /* blocksize for file system I/O */ 19 blkcnt_t st_blocks; /* number of 512B blocks allocated */ 20 time_t st_atime; /* time of last access */ 21 time_t st_mtime; /* time of last modification */ 22 time_t st_ctime; /* time of last status change */ 23 };
3、 文件類型及權限的判斷
1 The following POSIX macros are defined to check the file type using the st_mode field:
2
3 S_ISREG(m) is it a regular file?
4 S_ISDIR(m) directory?
5 S_ISCHR(m) character device?
6 S_ISBLK(m) block device?
7 S_ISFIFO(m) FIFO (named pipe)?
8 S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
9 S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
三、執行結果及對比
四、總結
總的來說,實現“ls -R”功能所涉及的特殊結構體很少,涉及知識面較窄,代碼量少,但細節部分較多,需要一定的經驗,經驗不足的話調試起來會比較麻煩,總體難度一般。在實現功能的過程中有幾點需要注意下:
1. 利用 lstat 函數讀取文件信息,否則無法有效處理軟連接文件;
2. 利用 rewinddir 函數重置指向文件流的指針,以便之后二次遍歷目錄;
3. 在讀取文件流時,注意屏蔽當前目錄(.)、上一級目錄(..)和隱藏文件(.*)。
目前暫未實現制表功能,有興趣的朋友可以嘗試着通過讀取終端顯示寬度與最長文件名長度來加以設計。
五、實現代碼
1、 myls2.h
1 #ifndef _MYLS2_H_ 2 #define _MYLS2_H_ 3 4 #include<stdio.h> 5 #include<stdlib.h> 6 #include<string.h> 7 #include<limits.h> 8 #include<unistd.h> 9 #include<dirent.h> 10 #include<sys/stat.h> 11 #include<sys/types.h> 12 #include<fcntl.h> 13 #include<time.h> 14 #include<pwd.h> 15 #include<grp.h> 16 17 // 處理錯誤 18 void error_printf(const char* ); 19 20 // 處理路徑下的文件 21 void list_dir(const char* ); 22 23 // 所顯示的文件信息 24 void display_dir(DIR* ); 25 26 #endif//_MYLS2_H_
2、myls2.c
1 #include "myls2.h" 2 3 // 處理錯誤 4 void error_printf(const char* funname) 5 { 6 perror(funname); 7 exit(EXIT_FAILURE); 8 } 9 10 // 讀取路徑下的文件 11 void list_dir(const char* pathname) 12 { 13 char nextpath[PATH_MAX+1]; 14 15 DIR* ret_opendir = opendir(pathname); // 打開目錄"pathname" 16 if(ret_opendir == NULL) 17 error_printf("opendir"); 18 19 printf("%s:\n",pathname); // 顯示pathname目錄路徑 20 display_dir(ret_opendir); // 顯示pathname目錄下所有非隱藏文件名稱 21 22 struct dirent* ret_readdir = NULL; // 定義readdir函數返回的結構體變量 23 while(ret_readdir = readdir(ret_opendir)) // 判斷是否讀取到目錄尾 24 { 25 char* filename = ret_readdir->d_name; // 獲取文件名 26 27 int end = 0; // 優化顯示路徑(處理"./test/"與"./test") 28 while(pathname[end]) 29 end++; 30 strcpy(nextpath,pathname); 31 if(pathname[end-1] != '/') 32 strcat(nextpath,"/"); 33 strcat(nextpath,filename); 34 35 struct stat file_message = {}; // 定義stat函數返回的結構體變量 36 int ret_stat = lstat(nextpath, &file_message); // 獲取文件信息 37 if(ret_stat == -1) // stat讀取文件錯誤則輸出提示信息 38 printf("%s error!", filename); 39 else if(S_ISDIR(file_message.st_mode) && filename[0]!='.') // 篩選"."、".."與隱藏文件 40 { 41 list_dir(nextpath); 42 } 43 } 44 closedir(ret_opendir); 45 } 46 47 // 打印所讀取文件的信息 48 void display_dir(DIR* ret_opendir) 49 { 50 struct dirent* ret_readdir = NULL; // 定義readdir函數返回的結構體變量 51 while(ret_readdir = readdir(ret_opendir)) // 判斷是否讀取到目錄尾 52 { 53 char* filename = ret_readdir->d_name; // 獲取文件名 54 if(filename[0]!='.') // 不輸出當前目錄、上一級目錄與隱藏文件 55 printf("%s\t",ret_readdir->d_name); // 打印文件名 56 } 57 rewinddir(ret_opendir); // 非常重要,將文件流的指針撥回起始位置 58 puts(""); 59 puts(""); 60 }
3、main_myls2.c
1 #include "myls2.h" 2 3 4 int main(const char argc, const char** argv) 5 { 6 char path[PATH_MAX+1] = {}; 7 8 if(argc == 2 && !(strcmp(argv[1],"-R"))) // 判斷命令格式 9 strcpy(path,"."); 10 else if(argc != 3) 11 { 12 printf("格式有誤! \n"); 13 exit(EXIT_FAILURE); 14 } 15 else 16 strcpy(path,argv[2]); 17 18 if(!(strcmp(argv[1],"-R"))) 19 { 20 struct stat file_message = {}; 21 int ret_stat = lstat(path, &file_message); 22 23 if(ret_stat == -1) 24 error_printf("stat"); 25 26 if(S_ISDIR(file_message.st_mode)) // 判斷是否為目錄 27 list_dir(path); 28 else 29 printf("It is not dir!"); 30 } 31 else 32 { 33 printf("error in main!\n"); 34 exit(EXIT_FAILURE); 35 } 36 return 0; 37 }