操作系统|Linux系统编程【3.1】——编写ls命令

ls命令简介 老规矩,直接在终端输入:man ls
(有关于man命令的简介可以参考笔者前期博客:Linux系统编程【1】——编写more命令)
操作系统|Linux系统编程【3.1】——编写ls命令
文章图片

可以看到,ls命令的作用是显示目录中的文件名,它带有可选的参数,如’-a’表示显示所有文件(包含隐藏文件,即以’.‘开头的文件),’-l’表示显示文件及文件属性等等。
操作系统|Linux系统编程【3.1】——编写ls命令
文章图片

本次博客就只专注于如何显示出目录中的文件名,而显示文件属性这方面的实现将写在下一篇博客中。
如何实现初级版ls命令 既然我们的目的是要显示出目录中的文件,基于Linux文件编程的思想,我们只需找到存放指定目录文件信息的那个文件,然后读取其中的内容并显示就可以了。
根据之前对于more和who命令的实现中:打开文件、读取文件、关闭文件的思路,可以猜测对于目录的处理也可能为:打开目录、读取目录、关闭目录。
确定工具函数
利用man -k dir查找到readdir(3)就是我们想要的(另外的两个readdir(2)和readdir_r(3) man进去看一下描述,确实不是我们需要的)。
操作系统|Linux系统编程【3.1】——编写ls命令
文章图片

由readdir的“SEE ALSO”引出来的还有,opendir和closedir:
操作系统|Linux系统编程【3.1】——编写ls命令
文章图片

操作系统|Linux系统编程【3.1】——编写ls命令
文章图片

这里插一句,我们之前的思路是找到存储目录信息的文件,然后对这个文件内容进行处理。但是笔者发现这些文件不容易找到,好在linux已经给我们提供了处理这种事的工具函数,如这个readdir函数,传入目录指针(由opendir函数获得,而opendir函数仅需传入目录名)就可以获得一个包含所需信息的结构体指针。
这就好比是你要的东西在仓库(存目录相关信息的文件)里,但是仓库的位置(文件路径)你一下找不到,并且里面放有各种东西(各种参数),要自己去挑选(选出自己所需的数据),最后再自己扛出来并关仓库门(格式处理、数据复制、关闭文件等等)。现在好了,有了几个代理人,他们对仓库很熟悉,一下子就能找到仓库位置(opendir函数),然后根据位置拿里面的东西并打包出来交给你(readdir函数),最后还替你关仓库门(closedir函数),多舒服的一件事情。
【操作系统|Linux系统编程【3.1】——编写ls命令】找到这三个代理人(opendir/readdir/closedir)后,把整套流程交给他们去做,我们拿到打包好的东西(struct dirent)再自己简单处理下就行了。
确定所需参数
readdir函数返回的是一个dirent结构体指针,这个dirent结构体中包含的d_name(文件名)就是我们需要的。
所以整个的ls命令实现流程为:
1.opendir打开指定目录
循环:{
2.readdir获得目录中每一个文件的dirent结构体
3.打印结构体中的d_name字符串
}
4.closedir关闭指定目录
ls命令源代码

/* * ls01.c * writed by lularible * 2021/02/07 */ #include #include #include #include #include//函数声明 void do_ls(const char*); void show_ls(struct dirent*); void error_handle(const char*); int main(int argc,char* argv[]) { //对输入的命令进行参数判断与处理 if(argc == 1){ //只输入了:ls do_ls("."); } else{while(--argc){do_ls(*(++argv)); } } return 0; }//主流程 void do_ls(const char* dir_name) { DIR* cur_dir; struct dirent* cur_item; //打开目录 if((cur_dir = opendir(dir_name)) == NULL){error_handle(dir_name); } else{//读取目录并显示信息 while((cur_item = readdir(cur_dir))){show_ls(cur_item); } printf("\n"); //关闭目录 closedir(cur_dir); } }//显示文件名 void show_ls(struct dirent* cur_item){ printf("%s",cur_item->d_name); printf("\n"); }//错误处理 void error_handle(const char* dir_name){ perror(dir_name); exit(1); }

增加可选参数"-a"和排序 现在对于ls做一点小小的优化:
  • 1.当不带任何参数时,显示的文件名不包括隐藏文件(以’.'开头的文件名),带上参数"-a"时,才把隐藏的文件名显示出来
  • 2.将文件名先按照字典序排序再显示
针对第一点,可以对输入的参数进行判断,依据判断结果进行不同的操作,这个很容易实现。
针对第二点,将文件名存到数组中,写一个字典序排序算法,对数组中文件名进行排序即可。
源代码
/* * ls02.c * writed by lularible * 2021/02/07 */#include #include #include #include #include #include //字典序排序算法//函数声明 void do_ls(const char*); void restored_ls(struct dirent*); void error_handle(const char*); //全局变量 int has_a = 0; //标记是否带'-a'参数 char *filenames[4096]; //存放文件名 int file_cnt = 0; //目录中文件个数int main(int argc,char* argv[]) { //对输入的命令进行参数判断与处理 if(argc == 1){ //只输入了:ls do_ls("."); } else{if(strcmp(argv[1],"-a") == 0){has_a = 1; --argc; ++argv; } if(argc == 1){do_ls("."); } else{while(--argc){do_ls(*(++argv)); } } } return 0; }void do_ls(const char* dir_name) { DIR* cur_dir; struct dirent* cur_item; //打开目录 if((cur_dir = opendir(dir_name)) == NULL){error_handle(dir_name); } else{//读取目录并显示信息 //将文件名存入数组 while((cur_item = readdir(cur_dir))){restored_ls(cur_item); } //字典序排序 sort(filenames,0,file_cnt-1); //输出结果 int i = 0; for(i = 0; i < file_cnt; ++i){printf("%s\n",filenames[i]); } //关闭目录 closedir(cur_dir); } }void restored_ls(struct dirent* cur_item){ char* result = cur_item->d_name; //当不带-a参数时,隐藏以'.'开头的文件 if(!has_a && *result == '.') return; filenames[file_cnt++] = cur_item->d_name; }void error_handle(const char* dir_name){ perror(dir_name); exit(1); }

其中的字典序排序算法如下:(利用快排的思想)
/* * sort.h * writed by lularible * 2021/02/07 */#include #include #includevoid swap(char** s1,char** s2); int compare(char* s1,char* s2); int partition(char** filenames,int start,int end); void sort(char** filenames,int start,int end); //交换两字符串 void swap(char** s1,char** s2) { char* tmp = *s1; *s1 = *s2; *s2 = tmp; }//比较两字符串的字典序 //s1靠前,返回负数,s1靠后,返回正数 //s1和s2完全一样,返回0 int compare(char* s1,char* s2){ while(*s1 && *s2 && *s1 == *s2){++s1; ++s2; } return *s1 - *s2; }int partition(char** filenames,int start,int end){ if(!filenames) return -1; char* privot = filenames[start]; while(start < end){while(start < end && compare(privot,filenames[end]) < 0) --end; swap(&filenames[start],&filenames[end]); while(start < end && compare(privot,filenames[start]) >= 0) ++start; swap(&filenames[start],&filenames[end]); } return start; }void sort(char** filenames,int start,int end){ if(start < end){int position = partition(filenames,start,end); sort(filenames,start,position - 1); sort(filenames,position + 1,end); } }

效果展示 操作系统|Linux系统编程【3.1】——编写ls命令
文章图片

与原版ls命令不同点 虽然已经基本实现了ls命令,但是与原版ls相比,还存在一些不同,比如输出的格式是一行一个文件名,原版的为一行多个且对齐,还有就是原版ls显示的不同文件带有不同的颜色。这个估计和文件类型有关,待下一篇博客仔细研究。
参考资料 《Understanding Unix/Linux Programming A Guide to Theory and Practice》
欢迎大家转载本人的博客(需注明出处),本人另外还有一个个人博客网站:lularible的个人博客,欢迎前去浏览。

    推荐阅读