当前位置 博文首页 > lularible:Linux系统编程【3.1】——编写ls命令

    lularible:Linux系统编程【3.1】——编写ls命令

    作者:lularible 时间:2021-02-07 20:25

    在笔者之前的Linux系统编程系列博客中【1】和【2】实现的more和who命令,是对于文件内容的读取与处理。而本次所要实现的是ls命令,ls命令的作用是显示目录中的内容,研究的对象是目录,这又会有哪些新知识点呢,让我们一起来看看吧

    ls命令简介

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

    yNVp0s.md.png

    可以看到,ls命令的作用是显示目录中的文件名,它带有可选的参数,如'-a'表示显示所有文件(包含隐藏文件,即以'.'开头的文件),'-l'表示显示文件及文件属性等等。

    yNZQbj.md.png

    本次博客就只专注于如何显示出目录中的文件名,而显示文件属性这方面的实现将写在下一篇博客中。

    如何实现初级版ls命令

    既然我们的目的是要显示出目录中的文件,基于Linux文件编程的思想,我们只需找到存放指定目录文件信息的那个文件,然后读取其中的内容并显示就可以了。

    根据之前对于more和who命令的实现中:打开文件、读取文件、关闭文件的思路,可以猜测对于目录的处理也可能为:打开目录、读取目录、关闭目录。

    确定工具函数

    利用man -k dir查找到readdir(3)就是我们想要的(另外的两个readdir(2)和readdir_r(3) man进去看一下描述,确实不是我们需要的)。

    yNnMrD.md.png

    由readdir的“SEE ALSO”引出来的还有,opendir和closedir:

    yNuJw4.md.png

    yNKSnU.md.png

    这里插一句,我们之前的思路是找到存储目录信息的文件,然后对这个文件内容进行处理。但是笔者发现这些文件不容易找到,好在linux已经给我们提供了处理这种事的工具函数,如这个readdir函数,传入目录指针(由opendir函数获得,而opendir函数仅需传入目录名)就可以获得一个包含所需信息的结构体指针。

    这就好比是你要的东西在仓库(存目录相关信息的文件)里,但是仓库的位置(文件路径)你一下找不到,并且里面放有各种东西(各种参数),要自己去挑选(选出自己所需的数据),最后再自己扛出来并关仓库门(格式处理、数据复制、关闭文件等等)。现在好了,有了几个代理人,他们对仓库很熟悉,一下子就能找到仓库位置(opendir函数),然后根据位置拿里面的东西并打包出来交给你(readdir函数),最后还替你关仓库门(closedir函数),多舒服的一件事情。

    找到这三个代理人(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<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<dirent.h>
    #include<string.h>
    
    //函数声明
    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<stdio.h>
    #include<stdlib.h>
    #include<sys/types.h>
    #include<dirent.h>
    #include<string.h>
    #include</home/lularible/bin/sort.h>	//字典序排序算法
    
    //函数声明
    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<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    
    void 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);
    	}
    }
    

    效果展示

    yNGPrq.md.png

    与原版ls命令不同点

    虽然已经基本实现了ls命令,但是与原版ls相比,还存在一些不同,比如输出的格式是一行一个文件名,原版的为一行多个且对齐,还有就是原版ls显示的不同文件带有不同的颜色。这个估计和文件类型有关,待下一篇博客仔细研究。

    参考资料

    《Understanding Unix/Linux Programming A Guide to Theory and Practice》

    欢迎大家转载本人的博客(需注明出处),本人另外还有一个个人博客网站:[https://www.lularible.cn],欢迎前去浏览。

    bk