7

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

 3 years ago
source link: http://www.cnblogs.com/lularible/p/14386358.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

ls命令简介

老规矩,直接在终端输入:man ls

(有关于man命令的简介可以参考笔者前期博客: Linux系统编程【1】——编写more命令 )

n6BneyJ.png!mobile

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

uqqim2r.png!mobile

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

如何实现初级版ls命令

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

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

确定工具函数

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

fqYFfey.png!mobile

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

A3m6jeB.png!mobile

FBZryiq.png!mobile

这里插一句,我们之前的思路是找到存储目录信息的文件,然后对这个文件内容进行处理。但是笔者发现这些文件不容易找到,好在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);
	}
}

效果展示

emm6ZnI.png!mobile

与原版ls命令不同点

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

参考资料

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

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


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK