Linux系統命令“ls -l”的實現
一、基本概念
1、“ls -l”的意義
以長格式顯示目錄下的內容列表。輸出的信息從左到右依次包括文件名,文件類型、權限模式、硬連接數、所有者、組、文件大小和文件的最后修改時間等。
例:-rw-rw-r-- 1 using using 3102 7月 22 17:06 test.c
drwxrwxr-x 2 using using 4096 7月 22 18:39 testdir
lrwxrwxrwx 1 using using 17 7月 22 18:43 shared -> /media/sf_shared/
其中深藍色為目錄文件,天藍色為軟連接文件(具體顏色和vimrc配置有關)。
第一字段:首字母代表的是文件類型 ,其中"-"為普通文件、"d"為目錄文件、"c"為字符設備文件、"b"為塊設備文件、"p"為管道文件、"l"為鏈接文件、"s"為socket文件。“rwx”分別代表擁有讀、寫和執行權限,“-”代表無對應權限。三個“rwx”依次代表文件所有者、文件所有者所在用戶組、其它用戶對文件擁有的權限。
第二字段:文件硬連接數量
第三字段:文件擁有者
第四字段:文件擁有者所在組
第五字段:文件大小(以字節為單位)
第六字段:文件最后更改時間
第七字段:文件名(若為鏈接文件則追加顯示其鏈接的原文件的路徑)
二、重要函數與結構體
1、目錄操作函數
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 };
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.) 10 11 12 The following flags are defined for the st_mode field: 13 14 S_IFMT 0170000 bit mask for the file type bit fields 15 S_IFSOCK 0140000 socket 16 S_IFLNK 0120000 symbolic link 17 S_IFREG 0100000 regular file 18 S_IFBLK 0060000 block device 19 S_IFDIR 0040000 directory 20 S_IFCHR 0020000 character device 21 S_IFIFO 0010000 FIFO 22 S_ISUID 0004000 set UID bit 23 S_ISGID 0002000 set-group-ID bit (see below) 24 S_ISVTX 0001000 sticky bit (see below) 25 S_IRWXU 00700 mask for file owner permissions 26 S_IRUSR 00400 owner has read permission 27 S_IWUSR 00200 owner has write permission 28 S_IXUSR 00100 owner has execute permission 29 S_IRWXG 00070 mask for group permissions 30 S_IRGRP 00040 group has read permission 31 S_IWGRP 00020 group has write permission 32 S_IXGRP 00010 group has execute permission 33 S_IRWXO 00007 mask for permissions for others (not in group) 34 S_IROTH 00004 others have read permission 35 S_IWOTH 00002 others have write permission 36 S_IXOTH 00001 others have execute permission
4、文件用戶ID與用戶所在組ID的轉換
1 #include <sys/types.h> 2 #include <pwd.h> 3 4 struct passwd *getpwnam(const char *name); 5 struct passwd *getpwuid(uid_t uid); 6 int getpwnam_r(const char *name, struct passwd *pwd,char *buf, size_t buflen, struct passwd **result); 7 int getpwuid_r(uid_t uid, struct passwd *pwd,char *buf, size_t buflen, struct passwd **result); 8 9 10 The passwd structure is defined in <pwd.h> as follows: 11 12 struct passwd { 13 char *pw_name; /* username */ 14 char *pw_passwd; /* user password */ 15 uid_t pw_uid; /* user ID */ 16 gid_t pw_gid; /* group ID */ 17 char *pw_gecos; /* user information */ 18 char *pw_dir; /* home directory */ 19 char *pw_shell; /* shell program */ 20 };
1 #include <sys/types.h> 2 #include <grp.h> 3 4 struct group *getgrnam(const char *name); 5 struct group *getgrgid(gid_t gid); 6 int getgrnam_r(const char *name, struct group *grp,char *buf, size_t buflen, struct group **result); 7 int getgrgid_r(gid_t gid, struct group *grp,char *buf, size_t buflen, struct group **result); 8 9 10 The group structure is defined in <grp.h> as follows: 11 12 struct group { 13 char *gr_name; /* group name */ 14 char *gr_passwd; /* group password */ 15 gid_t gr_gid; /* group ID */ 16 char **gr_mem; /* group members */ 17 };
5、文件最后修改時間
文件最后修改時間可以通過tm結構體接收localtime函數返回值來獲取。
1 #include <time.h> 2 3 struct tm *localtime(const time_t *timep); 4 struct tm *localtime_r(const time_t *timep, struct tm *result); 5 6 7 Broken-down time is stored in the structure tm which is defined in <time.h> as follows: 8 9 struct tm { 10 int tm_sec; /* seconds */ 11 int tm_min; /* minutes */ 12 int tm_hour; /* hours */ 13 int tm_mday; /* day of the month */ 14 int tm_mon; /* month */ 15 int tm_year; /* year */ 16 int tm_wday; /* day of the week */ 17 int tm_yday; /* day in the year */ 18 int tm_isdst; /* daylight saving time */ 19 };
三、執行結果及對比
四、總結
總的來說,實現“ls -l”功能所涉及的特殊結構體較多,基礎知識考察較多,需要構建很多小函數,較為繁雜,但邏輯結構簡單,沒有什么需要特別留意的地方,總體難度較低。
本博是在博友“Apollon_krj”的一篇博客“Linux&C編程之Linux系統命令“ls -l”的簡單實現”的基礎上改進完成。總體沿用了原有思路和框架,做了以下改良:
1. 可以處理軟連接文件(原處理鏈接文件所鏈接的原文件);
2. 當輸入“myls -l”指令時默認顯示當前目錄下文件的詳細信息(原報錯);
3. 指令、代碼優化。
但目前暫未實現總用量/total、模糊匹配和彩字顯示功能,有興趣的朋友可以嘗試一下。
五、實現代碼
1、myls.h
1 #ifndef _MYLS_H_ 2 #define _MYLS_H_ 3 4 #include<stdio.h> 5 #include<stdlib.h> 6 #include<string.h> 7 #include<unistd.h> 8 #include<dirent.h> 9 #include<sys/stat.h> 10 #include<sys/types.h> 11 #include<fcntl.h> 12 #include<time.h> 13 #include<pwd.h> 14 #include<grp.h> 15 16 // 處理錯誤 17 void error_printf(const char* ); 18 19 // 處理路徑下的文件 20 void list_dir(const char* ); 21 void list_message(const char* , const struct stat*); 22 23 // 所顯示的文件信息 24 void file_type(const struct stat* ); 25 void file_power(const struct stat* ); 26 // printf st_nlink 27 void file_id(const struct stat* ); 28 // printf st_size 29 void file_mtime(const struct stat* ); 30 // printf filename 31 void link_printf(const char* ); 32 33 #endif//_MYLS_H_
2、 myls.c
1 #include "myls.h" 2 3 // 處理錯誤 4 void error_printf(const char* funname) 5 { 6 perror(funname); 7 exit(EXIT_FAILURE); 8 /* 9 * EXIT_SUCCESS和EXIT_FAILURE是兩個常量。 10 * EXIT_SUCCESS=0,EXIT_FAILURE=1。 11 * 0表示程序壽終正寢,1表示死於非命。 12 */ 13 } 14 15 // 讀取路徑下的文件 16 void list_dir(const char* pathname) 17 { 18 DIR* ret_opendir = opendir(pathname); // 打開目錄"pathname" 19 if(ret_opendir == NULL) 20 error_printf("opendir"); 21 22 int ret_chdir = chdir(pathname); // 改變工作目錄至"pathname",便於stat函數的使用 23 if(ret_chdir == -1) 24 error_printf("chdir"); 25 26 struct dirent* ret_readdir = NULL; // 定義readdir函數返回的結構體變量 27 while(ret_readdir = readdir(ret_opendir)) // 判斷是否讀取到目錄尾 28 { 29 char* filename = ret_readdir->d_name; // 獲取文件名 30 struct stat file_message = {}; // 定義stat函數返回的結構體變量 31 int ret_stat = lstat(filename, &file_message); // 獲取文件信息 32 if(ret_stat == -1) // stat讀取文件錯誤則輸出提示信息 33 printf("%s error!", filename); 34 else if(strcmp(filename,".") && strcmp(filename,"..")) // 不輸出當前目錄與上一級目錄 35 list_message(filename, &file_message); 36 } 37 } 38 39 // 打印所讀取文件的信息 40 void list_message(const char* filename, const struct stat* file_message) 41 { 42 file_type(file_message); // 判斷打印文件類型 43 file_power(file_message); // 判斷並打印文件權限 44 printf("%d ", file_message->st_nlink); // 打印硬鏈接數 45 file_id(file_message); // 轉換並打印用戶id與組id 46 printf("%5ld ", file_message->st_size); // 打印文件大小 47 file_mtime(file_message); // 打印文件最后修改時間 48 printf("%s ", filename); // 打印文件名 49 if(S_ISLNK(file_message->st_mode)) // 如果是軟鏈接文件,打印其指向的位置 50 link_printf(filename); 51 puts(""); 52 } 53 54 55 // 所顯示的文件信息 56 void file_type(const struct stat* file_message) 57 { 58 //mode_t mode = (*get_message).st_mode; 59 mode_t mode = file_message->st_mode; 60 61 if (S_ISREG(mode)) printf("-"); // 普通文件 62 else if(S_ISDIR(mode)) printf("d"); // 目錄文件 63 else if(S_ISCHR(mode)) printf("c"); // 字符設備文件 64 else if(S_ISBLK(mode)) printf("b"); // 塊設備文件 65 else if(S_ISFIFO(mode)) printf("p"); // 管道文件 66 else if(S_ISLNK(mode)) printf("l"); // 鏈接文件 67 else printf("s"); // socket文件 68 } 69 70 void file_power(const struct stat* file_message) 71 { 72 mode_t mode = file_message->st_mode; 73 74 // 判斷USR權限 75 printf("%c", mode&S_IRUSR?'r':'-'); 76 printf("%c", mode&S_IWUSR?'w':'-'); 77 printf("%c", mode&S_IXUSR?'x':'-'); 78 79 // 判斷GRP權限 80 printf("%c", mode&S_IRGRP?'r':'-'); 81 printf("%c", mode&S_IWGRP?'w':'-'); 82 printf("%c", mode&S_IXGRP?'x':'-'); 83 84 // 判斷OTH權限 85 printf("%c", mode&S_IROTH?'r':'-'); 86 printf("%c", mode&S_IWOTH?'w':'-'); 87 printf("%c ", mode&S_IXOTH?'x':'-'); 88 } 89 90 void file_id(const struct stat* file_message) 91 { 92 // 根據用戶id獲取用戶名 93 struct passwd* pwd; 94 pwd = getpwuid(file_message->st_uid); 95 printf("%s ",pwd->pw_name); 96 97 // 根據組id獲取組名 98 struct group* grp; 99 grp = getgrgid(file_message->st_gid); 100 printf("%s ",grp->gr_name); 101 102 #if 0 103 struct passwd 104 { 105 char * pw_name; /* Username, POSIX.1 */ 106 char * pw_passwd; /* Password */ 107 __uid_t pw_uid; /* User ID, POSIX.1 */ 108 __gid_t pw_gid; /* Group ID, POSIX.1 */ 109 char * pw_gecos; /* Real Name or Comment field */ 110 char * pw_dir; /* Home directory, POSIX.1 */ 111 char * pw_shell; /* Shell Program, POSIX.1 */ 112 }; 113 114 struct group 115 { 116 char *gr_name; /* Group name */ 117 char *gr_passwd; /* password */ 118 __gid_t gr_gid; /* Group ID */ 119 char **gr_mem; /* Member list */ 120 } 121 #endif//0 122 } 123 124 void file_mtime(const struct stat* file_message) 125 { 126 struct tm* t = localtime(&file_message->st_mtime); 127 printf("%2d月 %2d %02d:%02d ", t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min); 128 } 129 130 void link_printf(const char* filename) 131 { 132 char buf[1024] = "123"; 133 if(0 == readlink(filename, buf, sizeof(buf))) 134 error_printf("readlink"); 135 printf("-> %s ",buf); 136 }
3、main_myls.c
1 #include "myls.h" 2 3 4 int main(const char argc, const char** argv) 5 { 6 char path[1024] = {}; 7 8 if(argc == 2 && !(strcmp(argv[1],"-l"))) // 判斷命令格式 9 strcpy(path,"./"); 10 else if(argc != 3) 11 { 12 printf("usage:ls -l pathname. \n"); 13 exit(EXIT_FAILURE); 14 } 15 else 16 strcpy(path,argv[2]); 17 18 if(!(strcmp(argv[1],"-l"))) 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 list_message(path, &file_message); 30 } 31 else 32 { 33 printf("error in main!\n"); 34 exit(EXIT_FAILURE); 35 } 36 return 0; 37 }