Linux下的 ls 命令的简单实现
2014-12-28 23:06
621 查看
一、实现要求
又到了期末的时候,Linux实践课的老师挺厚道,布置了算是一个入门的大作业:实现一个简单版本的ls命令。根据Linux技术与实践课程学习的基础知识,编写一个可以在Unix/Linux环境下运行的程序:ls2.c,功能类似于系统自带的ls命令。可以显示特定目录下的所有文件的属性信息,或者直接列出所有文件。感觉自己写的代码有点乱,但是功能都实现了,这点还不错。目录下的文件多了会发生段错误,不懂为啥,C语言就是这么那啥。再说吧~
具体要求如下:
不加 –l 参数,如“ls2 /home”,可以直接列出目录下所有的文件,并按照升序显示。显示在终端里有格式的缩进,即分栏处理。
加 –l 参数时,如“ls2 –l /home ”,会详细显示所有的文件信息,与 ls –l 的系统命令基本一致。每行显示一个文件或者文件夹的信息,属性依次是:文件类型,当前用户权限,当前用户组权限,其他用户权限,用户名,组名,链接数,文件大小(字节),修改的最后时间。
注意格式化输出,对齐。
所有的隐藏文件都不显示。
二、背景讲解
先要讲两个头文件:<dirent.h> 和 <sys/stat.h> ,都是 POSIX.1标准定义的unix类的头文件。1. <dirent.h>
第一个,dirent.h,是用来处理目录,包含了许多UNIX系统服务的函数原型,例如opendir函数、readdir函数.这里我们可以用来通过文件名打开,关闭,读取特定的文件夹信息,或者说,该目录的信息。可以参考ls_header.h的34开始的代码。/* opendir函数可以按照文件名打开一个文件, 并返回一个DIR型的指针,若是返回NULL则说明文件打开失败。 */ DIR *opendir(const char *pathname); /* 根据上面的DIR指针,我们可以读取这个目录,readdir会返回一个dirent的结构体指针。 如果不为NULL,说明下面还有文件,可以继续调用readdir来获取下一个文件。 我们可以用得到的dirent结构体指针,获取文件的一些信息。 */ struct dirent *readdir(DIR *dp); /* 关闭文件目录 */ int closedir(DIR *dirp);dirent结构体组成如下,不过这里只用到了d_name,其他的暂时没啥用。
struct dirent { long d_ino; /* inode number 索引节点号 */ off_t d_off; /* offset to this dirent 在目录文件中的偏移 */ unsigned short d_reclen; /* length of this d_name 文件名长 */ unsigned char d_type; /* the type of d_name 文件类型 */ char d_name [NAME_MAX+1]; /* file name 文件名,最长256字符 */ }
2. <sys/stat.h>
这个头文件里面提供一些有用的东西,能根据全路径的文件名获得一个文件(文件夹在Linux中也算是一个特殊的文件,也有各种读写执行的权限)。里面有用的是stat函数,可以传进去一个pathname,结果会保存在一个stat结构体,即buf中,如下:
int stat(const char *restrict pathname,struct stat *restrict buf);stat结构体可以得到很多关于这个文件的属性信息,也就是我们这里要用 ls 命令列举出来的信息。当然啦,还要转换一下才有可读性。ls_util里面的函数主要就是用来转换成我们要的字符串的。
结构如下:
struct stat{
mode_t st_mode; //文件类型和权限信息
ino_t st_ino; //i结点标识
dev_t st_dev; //device number (file system)
dev_t st_rdev; //device number for special files
nlink_t st_nlink; //符号链接数
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
off_t st_size; //size in bytes,for regular files
time_t st_st_atime; //最后一次访问的时间
time_t st_mtime; //文件内容最后一次被更改的时间
time_t st_ctime; //文件结构最后一次被更改的时间
blksize_t st_blksize; //best I/O block size
blkcnt_t st_blocks; //number of disk blocks allocated
};当然,我们这里只用到了这几个:
st_mode 文件类型和许可权限
st_nlink 文件链接数
st_uid 文件属主id
st_gid 文件属主所在组的id
st_size 文件的字节数
st_mtime 文件最后修改的时间
三、程序流程
程序实现有三个文件,ls2.c、ls_header.h、ls_util.h文章的最后有附上。这里一个一个讲。1. ls2.c中main函数的逻辑
首先,我们在运行程序的时候,会从命令中读取参数,保存在 argv 的二维数组中,如“./ls2 –l /home/halfish”,所以第三个参数就是我们要处理的目录名。在ls2.c中,主要处理这些参数,对应该去调用带参数(lsLong函数)或者是不带参数(lsShort)的函数。
注意这里用户可能没有输入目录,所以默认要是“.”,即当前目录,作为参数。
其次,还要区分,是显示目录还是显示文件的信息。
2. ls_header.h的函数
lsLong对读取的参数作进一步处理,在ls_long_file中得到单个文件的详细信息,并保存在自定义的结构体attribute_file中。然后做一次选择排序,并格式化输出。格式化输出时,要考虑每个属性值的缩进,所以要先遍历一次所有要输出的文件属性,得到最大的长度,并按照这个长度缩进才能让输出显得整齐。
lsShort逻辑简单一点,所有要显示的文件名存储在filenames中。稍微麻烦一点的是,要根据当前终端的大小分栏显示。可能要用到ls_util.h中的getTerminatorSize函数来读取终端的列数和行数。
3. ls_util.h头文件
定义很多函数,把dirent结构体中的信息转换成我们最后要显示的字符串。见代码中的注释吧~四、附上代码
ls2.c#include <stdio.h> #include "ls_header.h" int main(int argc, char **argv) { // 处理参数, lsLong是带有 -l 参数的, lsShort为没有-l参数的ls命令 int i; if (argc == 1) { /* ls . */ lsShort("./"); return 0; } if (argc == 2 && !strcmp(argv[1], "-l") ) { /* ls -l . */ lsLong("./"); return 0; } if (argc > 2 && !strcmp(argv[1], "-l") ) { /* ls -l dirname */ for(i = 2; i < argc; ++ i) { printf("%s:\n", argv[i]); lsLong(argv[i]); if(i != argc - 1) printf("\n"); } return 0; } else { /* ls dirname */ for (i = 1; i < argc; ++ i) { printf("%s:\n", argv[i]); lsShort(argv[i]); if(i != argc - 1) printf("\n"); } return 0; } return 0; }
ls_header.h
#include <stdio.h> #include <dirent.h> // for DIR, dirent #include <string.h> #include <sys/ioctl.h> #include <unistd.h> #include "ls_util.h" // struct struct attribute file_attribute[200]; // maximum 200 void lsLong(char *dirname) { DIR *mydir = opendir( dirname ); /* directory */ char filename[20]; int file_num = 0; if (mydir == NULL) { // 显示单个文件的详细信息 strcpy(filename, dirname); ls_long_file("./", filename, &file_attribute[0]); ++ file_num; } else { // 考虑用户输入文件夹没有输入反斜杠的情况 int len = strlen(dirname); if (dirname[len - 1] != '/') { dirname[len] = '/'; dirname[len+1] = '\0'; } // 循环得到当前目录下的所有文件名,并存储在自定义的结构体中 struct dirent *mydirent; /* file */ while ( (mydirent = readdir( mydir )) != NULL) { char filename[20]; strcpy(filename, mydirent->d_name); // 不能为隐藏文件 if (!strcmp(filename, ".") || !strcmp(filename, ".") || filename[0] != '.') { ls_long_file(dirname, filename, &file_attribute[file_num ++]); } } closedir( mydir ); } // 按照文件名排序 struct attribute temp; char filename1[20]; char filename2[20]; int i, j; for (i = 0; i < file_num; ++i) { for (j = i + 1; j < file_num; ++ j) { strcpy(filename1, file_attribute[i].filename); strcpy(filename2, file_attribute[j].filename); if ( strcmp(filename1, filename2) > 0) { temp = file_attribute[i]; file_attribute[i] = file_attribute[j]; file_attribute[j] = temp; } } } // 格式化输出时,考虑每个属性值的范围 int max_mode = 0; int max_links = 0; int max_user_name = 0; int max_group_name = 0; int max_size = 0; int max_mtime = 0; int max_filename = 0; int max_extra = 0; for (i = 0; i < file_num; ++ i) { if ( max_mode < strlen(file_attribute[i].mode) ) { max_mode = strlen(file_attribute[i].mode); } if (max_links < f(file_attribute[i].links)) { max_links = f(file_attribute[i].links); } if ( max_user_name < strlen(file_attribute[i].user_name) ) { max_user_name = strlen(file_attribute[i].user_name); } if ( max_group_name < strlen(file_attribute[i].group_name) ) { max_group_name = strlen(file_attribute[i].group_name); } if (max_size < f(file_attribute[i].size)) { max_size = f(file_attribute[i].size); } if ( max_mtime < strlen(file_attribute[i].mtime) ) { max_mtime = strlen(file_attribute[i].mtime); } if ( max_filename < strlen(file_attribute[i].filename) ) { max_filename = strlen(file_attribute[i].filename); } if ( max_extra < strlen(file_attribute[i].extra) ) { max_extra = strlen(file_attribute[i].extra); } } for (i = 0; i < file_num; ++i) { char format[50]; // 定义输出的格式 sprintf(format, "%%%ds %%%dd %%%ds %%%ds %%%dld %%%ds %%s%%s\n", max_mode, max_links, max_user_name, max_group_name, max_size, max_mtime); // 按照定义的输出格式输出 printf(format, file_attribute[i].mode, file_attribute[i].links, file_attribute[i].user_name, file_attribute[i].group_name, file_attribute[i].size, file_attribute[i].mtime, file_attribute[i].filename, file_attribute[i].extra); } } // 处理不带 -l 参数的 ls 命令 void lsShort(char *dirname) { DIR *mydir = opendir( dirname ); /* directory */ // 用来暂时存储要显示的目录下的所有文件名,可以看到最大可以支持200个文件,但是每个文件名最长为20 char filenames[200][20]; int file_num = 0; if (mydir == NULL) { // 直接显示该文件 printf("%s\n\n", dirname); return ; } else { // 循环检查下面有多少文件,并把文件名全部放到filenames数组里 struct dirent *mydirent; /* file */ while ( (mydirent = readdir( mydir )) != NULL) { char fname[20]; strcpy(fname, mydirent->d_name); if (fname[0] != '.' ) { strcpy(filenames[file_num], mydirent->d_name); file_num ++; } } closedir( mydir ); } // 文件名排序 int i, j; char temp[20]; for(i = 0; i < file_num; ++ i) { for(j = i+1; j < file_num; ++ j) { if(strcmp(filenames[i], filenames[j]) > 0) { strcpy(temp, filenames[i]); strcpy(filenames[i], filenames[j]); strcpy(filenames[j], temp); } } } // 确定所有文件里面最长的文件名的长度 int max_len = 0; for(i = 0; i < file_num; ++ i) { int len = strlen(filenames[i]); if(len > max_len) { max_len = len; } } // 得到当前终端的分辨率 int cols = 80; int lines = 24; getTerminatorSize(&cols, &lines); char format[20]; sprintf(format, "%%-%ds ", max_len); // 格式化输出,当长度大于终端的列数时,换行 int current_len = 0; for(i = 0; i < file_num; ++ i) { printf(format, filenames[i]); current_len += max_len + 2; if(current_len + max_len + 2 > cols) { printf("\n"); current_len = 0; } } printf("\n"); }
ls_util.h
#include <stdio.h> #include <sys/stat.h> // 这个头文件用来得到文件的详细信息 #include <string.h> #include <time.h> // 时间头文件 #include <pwd.h> // 用来得到用户名 #include <grp.h> // 用来得到组名 // 结构体,用来存储要输出的每个属性值 struct attribute { char mode[10]; // 文件属性和权限 int links; // 链接数 char user_name[20]; // 用户名 char group_name[20]; // 所在的用户组 long size; // 文件大小 char mtime[20]; // 最后修改的时间 char filename[255]; // 文件名 char extra[3]; // 用来显示时候要加 "*"(可以执行的文件) 或者 "/" (目录) 的额外字符串 }; // 计算整数 n 有几位 int f(long n) { int ret = 0; while(n) { n = n / 10; ++ ret; } return ret; } // 得到终端的列数和行数 void getTerminatorSize(int *cols, int *lines) { #ifdef TIOCGSIZE struct ttysize ts; ioctl(STDIN_FILENO, TIOCGSIZE, &ts); *cols = ts.ts_cols; *lines = ts.ts_lines; #elif defined(TIOCGWINSZ) struct winsize ts; ioctl(STDIN_FILENO, TIOCGWINSZ, &ts); *cols = ts.ws_col; *lines = ts.ws_row; #endif /* TIOCGSIZE */ } // 由 int 型的 mode,得到实际要显示的字符串 void mode2str(int mode, char str[]) { strcpy(str, "----------\0"); if(S_ISDIR(mode)) str[0] = 'd'; if(S_ISCHR(mode)) str[0] = 'c'; if(S_ISBLK(mode)) str[0] = 'b'; if(S_ISLNK(mode)) str[0] = 'l'; if(mode & S_IRUSR) str[1] = 'r'; if(mode & S_IWUSR) str[2] = 'w'; if(mode & S_IXUSR) str[3] = 'x'; if(mode & S_IRGRP) str[4] = 'r'; if(mode & S_IWGRP) str[5] = 'w'; if(mode & S_IXGRP) str[6] = 'x'; if(mode & S_IROTH) str[7] = 'r'; if(mode & S_IWOTH) str[8] = 'w'; if(mode & S_IXOTH) str[9] = 'x'; } // 根据用户的 id 值,得到用户名 void uid2str(uid_t uid, char *user_name) /* 将uid转化成username */ { struct passwd *pw_ptr; pw_ptr = getpwuid(uid); if( pw_ptr == NULL) { sprintf(user_name, "%d", uid); } else { strcpy(user_name, pw_ptr->pw_name); } } // 根据用户组的 id 值,得到用户组名 void gid2str(gid_t gid, char *group_name) /* 将uid转化成username */ { struct group *grp_ptr; grp_ptr = getgrgid(gid); if( grp_ptr == NULL) { sprintf(group_name, "%d", gid); } else { strcpy(group_name, grp_ptr->gr_name); } } // 时间的格式化字符串, 注意这里我把前面的星期和后面的年份都去掉了 void time2str(time_t t, char *time_str) { strcpy( time_str, ctime(&t) + 4); time_str[12] = '\0'; } // 要显示的某一个文件详细信息,并把信息放在结构体 attribute 中 void ls_long_file(char *dirname, char *filename, struct attribute *file_attri) { // 根据文件夹名和文件名得到全名 char fullname[256]; strcpy(fullname, dirname); strcpy(fullname + strlen(dirname), filename); struct stat mystat; if ( stat(fullname, &mystat) == -1) { printf("ls_long_file: stat error\n"); } else { // 这里参考 <stat.h> 头文件 int mode = (int) mystat.st_mode; int links = (int) mystat.st_nlink; int uid = (int) mystat.st_uid; int gid = (int) mystat.st_gid; long size = (long) mystat.st_size; long mtime = (long) mystat.st_mtime; char str_mode[10]; /* 文件类型和许可权限, "drwxrwx---" */ char str_user_name[20]; char str_group_name[20]; char str_mtime[20]; // 这里就是直接调用上面的函数啦,洒家就不解释了 mode2str(mode, str_mode); uid2str(uid, str_user_name); gid2str(gid, str_group_name); time2str(mtime, str_mtime); char extra[3] = "\0\0"; if (str_mode[0] == 'd') { extra[0] = '/'; } else if (str_mode[0] == '-' && str_mode[3] == 'x') { extra[0] = '*'; } // 存储在结构体中 strcpy(file_attri->mode, str_mode); file_attri->links = links; strcpy(file_attri->user_name, str_user_name); strcpy(file_attri->group_name, str_group_name); file_attri->size = size; strcpy(file_attri->mtime, str_mtime); strcpy(file_attri->filename, filename); strcpy(file_attri->extra, extra); // 大概要得到的效果就是下面的情况啦! /* drwxr-xr-x 5 halfish halfish 4096 12月 28 10:35 Downloads/ */ } }
相关文章推荐
- Linux(Fedora)下ls命令的简单实现
- 使用linux-c编程实现简单的ls命令
- Linux下ls命令的简单模拟实现
- Linux下ls命令的简单实现
- 使用linux-c编程实现简单的ls命令
- Linux命令简单实现 -- ls
- Linux 简单实现 ls -l 命令
- Linux系统编程_1_目录读取(实现简单ls命令)
- linux ls命令的的简单实现APUE-1.4 文件和目录
- Linux&C编程之Linux系统命令“ls -l”的简单实现
- Linux命令的实现 -- ls pwd cd
- Linux---Ls命令 初级实现
- linux下实现我自己简单的 cp 命令
- linux 函数实现ls -l命令
- 做linux第四次实验,看错实验要求,花了一个下午自己模拟着实现一个ls命令。。。。
- linux编程实践4(实现ls -l命令)
- linux编程:ls命令的简单实现
- ls命令是怎样实现的,getdents64,linux-2.6.27.5
- Linux---Ls命令 初级实现
- Linux命令简单实现 -- pwd