Linux系统编程【3.2】——ls命令优化版和ls -l实现

前情提要

在笔者的上一篇博客Linux系统编程【3.1】——编写ls命令中,实现了初级版的ls命令,但是与原版ls命令相比,还存在着显示格式和无颜色标记的不同。经过笔者近两天的学习,基本解决了这两个问题,且实现了"ls -l",并对于可选参数"-a"和"-l"有了更好的支持(不管-a,-l输入顺序如何,是"ls -a -l",还是"ls -l -a",还是"ls -al",亦或是"ls -ls",出现位置几何,重复与否,都能正确运行)。

ls显示格式的解决

首先,让我们来观察一下原版ls显示的格式:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
笔者总结出的原版ls显示规律:

  • 1.按序显示
  • 2.按照排序规则按列从上往下显示,当前列显示完成后转到下一列继续显示
  • 3.每一列都是左对齐的
  • 4.每一列的宽度都是该列最长文件名长度加2
  • 5.列的数目要尽量保证每一行都被文件名“填充满”,而又不会导致行中最后一个文件名换行

在我们自己实现ls显示时,如何满足这5个规律呢?

对于规律1,笔者已经采用排序算法来满足了。规律3可以采用printf函数中的"%-*s"来满足。其他三条规律好像不太好满足。问题的关键在于,我们不知道要显示的行数和列数,以及每一列的最大字符串长度。为了获得这些数据,接下来介绍笔者琢磨出来的一种算法,姑且将其称为“分栏算法”吧。

分栏算法

问题可以简化为:给你一个已经排序的字符串指针数组,求能满足上述5个规律输出显示这些字符串的行/列数,以及每一列的最大字符串长度。

那么就让我们来构建这样一个函数签名:
由于要返回三个不同数据,所以将其中一个作为返回值,另外两个作为指针传入参数。

1.确定函数返回值:返回行数
2.确定函数参数:字符串指针数组、字符串个数、列数指针、每列的最大字符串长度数组

函数签名如下所示:

int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr);

"囫囵吞枣"版分栏算法

在函数主体中,先计算出

最少占用字符数 =(每个字符串长度+2)* 总字符串个数

加2是因为在显示时每个字符串后面至少跟两个空格。利用ioctl函数调用,获取当前屏幕宽度(一行能容纳的字符数)。用最少占用字符数除以屏幕宽度,得到一个基准行数。这个基准行数是最少所需行数,因为只有在每个字符串长度都一致,且加2之后的长度能整除屏幕宽度时,才只需要这么少的行数就能装得下。

当字符串之间长度差距较大时,所需的空间就越多。比如同一列中,一个字符串老长了,其他的比较短,为了满足规律4,那么短的字符串后面跟的空格数就大大增加。

获得基准行数之后,预分配基准行数*屏幕宽度的字符数大小,就将字符串一一从上到下(排基准行数行),从左到右排列(列数保存给列数指针),同时对于每一列都获取最大字符串长度(存到每列最大字符串长度数组中),重新计算所占总字符数时,将每一列最大字符串长度乘以行数的值算进去。然后将所占总字符数减去预分配的大小,继续分配足够的行来容纳这个差值。

迭代进行上述步骤,直到所占总字符数不大于预分配的大小。返回最后分配的行数。

如下所示为图解:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
之所以说它是“囫囵吞枣”,是因为在处理超额长度时,直接取最长的那一个代表最后一列的所有字符串长度,一刀切了。这可能导致在某些情况下格式显示不正确。

源代码如下:

int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr)
{
    //获取路径名中的文件名之前的长度
    int path_len = strlen(filenames[0]);
    while(filenames[0][path_len] != '/'){
        --path_len;
    }
	++path_len;
	struct winsize size;
    ioctl(STDIN_FILENO,TIOCGWINSZ,&size);
    int col = size.ws_col;      //获得当前窗口的宽度(字符数)
    int col_max = 0;
    int cur_file_size = 0;		
	int filenames_len[file_cnt];
	*cal_col = 0;
	int i = 0;
	//获得每个文件名的字符长度
	for(i = 0;i < file_cnt;++i){
		filenames_len[i] = strlen(filenames[i]) - path_len;	
		cur_file_size += (filenames_len[i] + 2);	//计算所有文件名字符数加两个空格的总字符数
        }                   

	//最小行数,在此基础上迭代
	int base_row = cur_file_size / col;
	if(cur_file_size % col){
		base_row++;
	}
	int pre_allc_size = 0;
	do{
		*cal_col = 0;
		pre_allc_size = base_row * col;
		cur_file_size = 0;
		for(i = 0;i < file_cnt;++i){
			//当前列的最后一行
			if(i % base_row == base_row - 1){
				++(*cal_col);
				col_max = (filenames_len[i] > col_max) ? filenames_len[i] : col_max;
				cur_file_size += (col_max + 2) * base_row;
				col_max_arr[*cal_col-1] = col_max;
				col_max = 0;
			}
			//非最后一行
			else{
				col_max = (filenames_len[i] > col_max) ? filenames_len[i] : col_max;	
                        }   
		}
		//最后一列未满
		if(i % base_row){
			++(*cal_col);
			cur_file_size += (col_max + 2) * (i % base_row);
			col_max_arr[*cal_col-1] = col_max;
			col_max = 0;
		}
		int dis = 0;
		if(cur_file_size > pre_allc_size){
			dis = cur_file_size - pre_allc_size;
		}
		base_row += (dis / col);
		if(dis % col){
			++base_row;
		}
	}while(cur_file_size > pre_allc_size);
	return base_row;
}

在进行输出时,从左往右,从上到下打印,后续在代码中体现。

"精打细算"版分栏算法

在计算基准行数时,与"囫囵吞枣"的分栏算法一样。区别就在于对于超额列的处理上。设定一个当前屏幕剩余宽度变量,对于每一个字符串,当其长度超过剩余宽度,则终止排列,所需的额外空间大小为剩余未排列的所有字符串块长度之和。

下图所示为图解:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
正因为计算超额空间时,算的是每个未排列字符串块长度的真实长度,所以是“精打细算”。并且每次都检测是否超剩余宽度,所以能严格保证最终显示格式的正确性。

源代码如下:

int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr)
{
    //获取路径名中的文件名之前的长度
    int path_len = strlen(filenames[0]);
    while(filenames[0][path_len] != '/'){
        --path_len;
    }
	++path_len;
	struct winsize size;
    ioctl(STDIN_FILENO,TIOCGWINSZ,&size);
    int col = size.ws_col;      //获得当前窗口的宽度(字符数)
    int col_max = 0;
    int cur_file_size = 0;		
	int filenames_len[file_cnt];
	*cal_col = 0;
	int i = 0;
	int j = 0;
	//获得每个文件名的字符长度
	for(i = 0;i < file_cnt;++i){
		filenames_len[i] = strlen(filenames[i]) - path_len + 2;		//字符串至少带两个空格
		/*特殊情况:当最大字符串长度比屏幕宽度大时,直接返回行数:file_cnt,列数:1,最大宽度:最大的字符串长度*/
		if(filenames_len[i] > col){
			*cal_col = 1;
			col_max_arr[0] = filenames_len[i];
			return file_cnt;
		}				
		cur_file_size += filenames_len[i];
	}	

	//最小行数,在此基础上迭代
	int base_row = cur_file_size / col;
	if(cur_file_size % col){
		base_row++;
	}
	int flag_succeed = 0;		//标记是否排列完成
	//开始排列
	while(!flag_succeed){
		int remind_width = col;	//当前可用宽度
		*cal_col = -1;
		for(i = 0;i < file_cnt;++i){
			/*如果剩余宽度不足以容纳当前字符串,则跳出并分配额外的行空间*/
			if(filenames_len[i] > remind_width){
				break;	
			}
			//新起的一列
			if(i % base_row == 0){
				++(*cal_col);
				col_max_arr[*cal_col] = filenames_len[i]; 
			}
			else{
				col_max_arr[*cal_col] = (filenames_len[i] > col_max_arr[*cal_col]) ? filenames_len[i] : col_max_arr[*cal_col];
			}
			//最后一行,更新剩余的宽度
			if(i % base_row == base_row - 1){
				remind_width -= col_max_arr[*cal_col];	
			}
		}
		//判断是否排列完成
		if(i == file_cnt){
			flag_succeed = 1;
		}
		//再分配额外行空间
		else{
			int extra = 0;						//所需额外的字符数
			while(i < file_cnt){
				extra += filenames_len[i++];
			}
			if(extra % col){
				base_row += (extra / col + 1);
			}
			else{
				base_row += (extra / col);
			} 
		}
	}   
	++(*cal_col);								//列标从0开始,所以最后加1
	return base_row;
}

分栏效果展示与不足

两种分栏算法的比对

1."囫囵吞枣"版分栏算法和原版ls的对比
Linux系统编程【3.2】——ls命令优化版和ls -l实现
前一个是原版ls命令显示效果,后一个是笔者实现的ls03("囫囵吞枣"版)命令显示效果,基本一致。

2."精打细算"版分栏算法和"囫囵吞枣"版分栏算法比对

之前提到过在某些情况下,"囫囵吞枣"版分栏算法格式显示不正确。下图就是一个例子:

Linux系统编程【3.2】——ls命令优化版和ls -l实现
可以看到,"囫囵吞枣(ls03)"显示格式不对,但"精打细算(ls04)"就没问题。所以"精打细算"版分栏算法是"囫囵吞枣"版分栏算法的优化。

不足之处

目前来看,即使是更厉害的"精打细算"版分栏算法,也存在两个主要的不足点。

其一是排序与原版不同,笔者利用的是ASCII值来进行字符串排序的,对于大小写字母,特殊符号和汉字,没有做到像原版ls那样合适。

其二是在显示汉字文件名时,格式不正确,如下所示:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
显示出来的存在没有左对齐的列。笔者推测,汉字的字符串单位长度和显示出的所占宽度不是一比一的关系,导致计算时存在偏差。进一步推测,除了汉字,对于其他的特殊符号,只要单位长度和显示出的所占宽度不是一比一,都会存在偏差。如果想要解决这个问题,就要查找这个对应关系,然后进行特定的转换。

ls显示颜色的解决

因为要查找有关颜色方面的信息,笔者通过linux指导信息,确定dircolors这个命令可以给我提供有用的信息,输入:

man dircolors

显示信息如下:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
其中有一个选项是:dircolors -p,能够打印出所有默认的颜色信息。我们输入:

dircolors -p > color

">"表示将dircolors -p命令的输出结果保存到color文件中(若没有则自动创建)。

然后输入:

vim color

就可以进入文件查看其中内容了(当然用"more color"命令也行,只是笔者习惯用"vim"命令),如下所示:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
对于每种类型的文件,都给出了默认的颜色值。
其中关于c语言颜色打印,为了防止本文篇幅过长,影响阅读体验,可自行查阅资料,这里随便给出一个链接供参考:
printf打印颜色

接下来的事情就是如何获得指定的文件类型,到这里就引出了下一节内容:ls -l的实现。

ls -l的实现

获取stat结构体内容

首先看一下原版ls -l的输出内容:
Linux系统编程【3.2】——ls命令优化版和ls -l实现

  • 第一部分:模式(mode),每行的第一个字符表示文件类型。"-"表示普通文件,"d"表示目录等等。接下来的9个字符表示文件访问权限,分为读权限、写权限和执行权限,又分别针对3种对象:用户、同组用户和其他用户。
  • 第二部分:链接数列(links),表示该文件被引用次数。
  • 第三部分:文件所有者(owner),表示文件所有者的用户名
  • 第四部分:组(group),表示文件所有者所在的组名
  • 第五部分:大小(size),表示文件所占字节数
  • 第六部分:最后修改时间(laste-modified)
  • 第七部分:文件名(name)

如何获得这些信息?笔者通过查阅书籍,知道了一个"stat"函数,能帮助我们拿到上述信息。

输入:

man 2 stat

ps:直接输入man stat得到的是stat(1),是一个终端命令,不是我们想要的,在其中的“SEE ALSO”找到的stat(2)才是我们需要的。

Linux系统编程【3.2】——ls命令优化版和ls -l实现
将文件路径传入stat函数,可以获得一个stat类型的结构体,该结构体定义如下:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
太棒了,我们要的信息它都有!

格式转换

但是不要高兴的太早,如果直接打印这个stat结构体里面的st_mode、st_uid、st_gid和st_mtime,显示的是一些数字,所以还要对它们一一转换成字符串再输出。

st_mode位运算

st_mode实际上是一个16位的二进制数,其结构如下:
Linux系统编程【3.2】——ls命令优化版和ls -l实现
Linux中文件被分为7大类,也就是有7种不同的编码,我们只要提取其中的type位,然后与定义的文件类比对就能知道该文件类型了。同样的,对于权限位,只要此位为1,就置为'r'、'w'或'x',否则置为'-'。

如何判断特定的位的值呢?这里就需要掩码这个东西了,笔者在计算机网络中学过类似的玩意--ip地址掩码。掩码的作用就是将其他位遮挡住,只暴露所需的位。即掩码将其他的位设为0,所需的位设为1,利用"与"运算(&),把待处理的数和掩码相与,将结果与预先存放的数对比看是否一致。

//定义判断文件类型的宏
#define S_ISFIFO(m)	(((m)&(0170000))==(0010000))	
#define S_ISDIR(m) (((m)&(0170000))==(0040000))		
#define S_ISCHR(m) (((m)&(0170000))==(0020000))		
#define S_ISBLK(m) (((m)&(0170000))==(0060000))
#define S_ISREG(m) (((m)&(0170000))==(0100000))
#define S_ISLNK(m) (((m)&(0170000))==(0120000))
#define S_ISSOCK(m) (((m)&(0170000))==(0140000))
#define S_ISEXEC(m) (((m)&(0000111))!=(0))

举例来说:上图中第一个掩码是0170000,第一个0表示这个数是八进制的,转换为二进制为1111000000000000,即type位全部为1,假设st_mode为0001000000000000,相与之后得到0001000000000000,即八进制的0100000,查表就知道这是一个regular文件。

同理,上图中第二个掩码是0000111,它把'x'位设为1,其他位为0,与st_mode相与后,如果st_mode中任意一个'x'位为1,那么结果就不等于0,由此可以判断文件是否为可执行文件。

这样,我们就可以利用掩码和预定义的值来协助完成模式字符串的转换。

st_uid/st_gid/st_mtime格式转换

借助getpwuid函数,getgrgid函数和ctime函数分别完成用户名、组名和时间格式的转换。

ls -l效果展示

Linux系统编程【3.2】——ls命令优化版和ls -l实现

源码

ls可选参数:-a ,-l

/*
 * ls04.c
 * writed by lularible
 * 2021/02/09
 * ls -a -l	
 */
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<sys/types.h>
#include<dirent.h>
#include<string.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<termios.h>
#include<sys/stat.h>
#include<time.h>
#include</home/lularible/bin/sort.h>
#include<pwd.h>
#include<grp.h>
/*
//定义判断文件类型的宏,系统中已经定义过了,这里写出来供参考
#define S_ISFIFO(m)	(((m)&(0170000))==(0010000))	
#define S_ISDIR(m) (((m)&(0170000))==(0040000))		
#define S_ISCHR(m) (((m)&(0170000))==(0020000))	
#define S_ISBLK(m) (((m)&(0170000))==(0060000))
#define S_ISREG(m) (((m)&(0170000))==(0100000))
#define S_ISLNK(m) (((m)&(0170000))==(0120000))
#define S_ISSOCK(m) (((m)&(0170000))==(0140000))
*/
#define S_ISEXEC(m) (((m)&(0000111))!=(0))
//函数声明
void do_ls(const char*);						//主框架
void error_handle(const char*);					//错误处理
void restored_ls(struct dirent*,const char*);	//暂存文件名
void color_print(char*,int,int);				//根据文件类型打印不同颜色的文件名
void showtime(long);
void show_with_l();								//显示带-l参数的内容
void show_without_l();							//显示不带-l参数的内容
int cal_row(char**,int,int*,int*);				//为分栏算法提供要显示的行/列数
void mode_to_letters(int,char*);				//-l中,转换mode属性为字符串
char* uid_to_name(uid_t);						//-l中,转换uid为字符串
char* gid_to_name(gid_t);						//-l中,转换gid为字符串

//全局变量
int a_flag = 0;			//是否带-a参数
int l_flag = 0; 		//是否带-l参数
char* dirnames[4096];	//暂存目录名
int dir_cnt = 0;		//目录个数
char* filenames[4096];	//暂存目录中文件名
int file_cnt = 0;

int main(int argc,char* argv[])
{
	//存储文件名和所带参数
	while(--argc){
		++argv;
		//遇到可选参数
		if(*argv[0] == '-'){
			while(*(++(*argv))){
				if(**argv == 'a'){
					a_flag = 1;
				}
				else if(**argv == 'l'){
					l_flag = 1;
				}
				else{
					error_handle(*argv);
				}
			}
		}
		//遇到目录名
		else{
			dirnames[dir_cnt++] = *argv; 
		}
	}
	if(dir_cnt == 0){
		dirnames[dir_cnt++] = ".";
	}
	sort(dirnames,0,dir_cnt - 1);
	//对filenames中文件名,对于不同的可选参数的处理
	int i = 0;
	DIR* cur_dir;
	struct diren* cur_item;
	//遍历目录文件
	for(i = 0;i < dir_cnt;++i){
		printf("以下是”%s“的内容:\n",dirnames[i]);
		do_ls(dirnames[i]);
	}
}

void do_ls(const char* dir_name)
{
    DIR* cur_dir;
    struct dirent* cur_item;
	file_cnt = 0;
	//打开目录
    if((cur_dir = opendir(dir_name)) == NULL){
        error_handle(dir_name);
    }
    else{
       	//读取目录并显示信息
    	//将文件名存入数组
		while((cur_item = readdir(cur_dir))){
        	restored_ls(cur_item,dir_name);
       	}
       	//字典序排序
       	sort(filenames,0,file_cnt-1);
		//输出结果
		if(l_flag){
			//带-l参数
	   		show_with_l();
		}
		else{
			//不带-l参数
			show_without_l();
		}
		//释放内存
		int i = 0;
		for(i = 0;i < file_cnt;++i){
			free(filenames[i]);
		}
       	//关闭目录
       	closedir(cur_dir);
   	 }
}



void restored_ls(struct dirent* cur_item,const char* dir_name)
{
	char* file_path = (char*)malloc(sizeof(char)*256);
	strcpy(file_path,dir_name);
    char* result = cur_item->d_name;
	strcat(file_path,"/");
	strcat(file_path,result);
    //当不带-a参数时,隐藏以‘.'开头的文件
    if(!a_flag && *result == '.')    return;
    filenames[file_cnt++] = file_path;
}

void show_without_l()
{
	int i = 0;
	int j = 0;
	struct stat file_info;		//保存文件属性的结构体
	//分栏算法
	int col = 0;
	int col_max_arr[256];
	int row = cal_row(filenames,file_cnt,&col,col_max_arr);
	for(i = 0;i < row;++i){
		for(j = 0;j < col;++j){
			if((j*row+i) < file_cnt){
				if(stat(filenames[j*row+i],&file_info) != -1){	
					int mode = file_info.st_mode;
					color_print(filenames[j*row+i],mode,col_max_arr[j]);
				}
				else{
					error_handle(filenames[j*row+i]);
				}
			}
		}
		printf("\n");
	}
}

void show_with_l()
{
	struct stat info;
	char modestr[11];
	int i = 0;
	for(i = 0;i < file_cnt;++i){
			if(stat(filenames[i],&info) == -1){
				error_handle(filenames[i]);
			}
			else{
				mode_to_letters(info.st_mode,modestr);
				printf("%s",modestr);
				printf("%4d  ",(int)info.st_nlink);
				printf("%-12s",uid_to_name(info.st_uid));
				printf("%-12s",gid_to_name(info.st_gid));
				printf("%-8ld  ",(long)info.st_size);
				showtime((long)info.st_mtime);
				color_print(filenames[i],info.st_mode,0);
				printf("\n");
			}
	}
}

void showtime(long timeval)
{
	char* tm;
	tm = ctime(&timeval);
	printf("%24.24s  ",tm);
}

void color_print(char* pathname,int filemode,int width)
{
	//截取路径名中的文件名
	char* filename;
	int len = strlen(pathname);
	while(pathname[len] != '/'){
		--len;
	}
	filename = &pathname[len+1];

	if(S_ISDIR(filemode)){
		printf("\033[01;34m%-*s\033[0m",width,filename);
	}
	else if(S_ISLNK(filemode)){
		printf("\033[01;36m%-*s\033[0m",width,filename);
	}
	else if(S_ISFIFO(filemode)){
		printf("\033[40;33m%-*s\033[0m",width,filename);
	}
	else if(S_ISSOCK(filemode)){
		printf("\033[01;35m%-*s\033[0m",width,filename);
	}
	else if(S_ISBLK(filemode)){
		printf("\033[40;33;01m%-*s\033[0m",width,filename);
	}
	else if(S_ISCHR(filemode)){
		printf("\033[40;33;01m%-*s\033[0m",width,filename);
	}
	else if(S_ISREG(filemode)){
		if(S_ISEXEC(filemode)){
			printf("\033[01;32m%-*s\033[0m",width,filename);
		}
		else{
			printf("%-*s",width,filename);
		}
	}
	else{
		printf("%-*s",width,filename);
	}
}

int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr)
{
    //获取路径名中的文件名之前的长度
    int path_len = strlen(filenames[0]);
    while(filenames[0][path_len] != '/'){
        --path_len;
    }
	++path_len;
	struct winsize size;
    ioctl(STDIN_FILENO,TIOCGWINSZ,&size);
    int col = size.ws_col;      //获得当前窗口的宽度(字符数)
    int col_max = 0;
    int cur_file_size = 0;		
	int filenames_len[file_cnt];
	*cal_col = 0;
	int i = 0;
	int j = 0;
	//获得每个文件名的字符长度
	for(i = 0;i < file_cnt;++i){
		filenames_len[i] = strlen(filenames[i]) - path_len + 2;		//字符串至少带两个空格
		//特殊情况:当最大字符串长度比屏幕宽度大时,直接返回行数:file_cnt,列数:1,最大宽度:最大的字符串长度
		if(filenames_len[i] > col){
			*cal_col = 1;
			col_max_arr[0] = filenames_len[i];
			return file_cnt;
		}				
		cur_file_size += filenames_len[i];
	}
	//最小行数,在此基础上迭代
	int base_row = cur_file_size / col;
	if(cur_file_size % col){
		base_row++;
	}
	int flag_succeed = 0;		//标记是否排列完成
	//开始排列
	while(!flag_succeed){
		int remind_width = col;	//当前可用宽度
		*cal_col = -1;
		for(i = 0;i < file_cnt;++i){
			//如果剩余宽度不足以容纳当前字符串,则跳出并分配额外的行空间
			if(filenames_len[i] > remind_width){
				break;	
			}
			//新起的一列
			if(i % base_row == 0){
				++(*cal_col);
				col_max_arr[*cal_col] = filenames_len[i]; 
			}
			else{
				col_max_arr[*cal_col] = (filenames_len[i] > col_max_arr[*cal_col]) ? filenames_len[i] : col_max_arr[*cal_col];
			}
			//最后一行,更新剩余的宽度
			if(i % base_row == base_row - 1){
				remind_width -= col_max_arr[*cal_col];	
			}
		}
		//判断是否排列完成
		if(i == file_cnt){
			flag_succeed = 1;
		}
		//再分配额外行空间
		else{
			int extra = 0;						//所需额外的字符数
			while(i < file_cnt){
				extra += filenames_len[i++];
			}
			if(extra % col){
				base_row += (extra / col + 1);
			}
			else{
				base_row += (extra / col);
			} 
		}
	}   
	++(*cal_col);								//列标从0开始,所以最后要加1
	return base_row;
}

void mode_to_letters(int mode,char* str)
{
	strcpy(str,"----------");		//文件类型与权限,先占好10个坑位
	//这里只转换7种文件类型常见的三种
	if(S_ISDIR(mode)) str[0] = 'd';
	if(S_ISCHR(mode)) str[0] = 'c';
	if(S_ISBLK(mode)) str[0] = 'b';

	//权限转换
	//当前用户权限
	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';
}

char* uid_to_name(uid_t uid)
{
	struct passwd* pw_ptr;
	static char numstr[10];

	if((pw_ptr = getpwuid(uid)) == NULL){
		sprintf(numstr,"%d",uid);
		return numstr;
	}
	else{
		return pw_ptr->pw_name;
	}
}

char* gid_to_name(gid_t gid)
{
	struct group* grp_ptr;
	static char numstr[10];

	if((grp_ptr = getgrgid(gid)) == NULL){
		sprintf(numstr,"%d",gid);
		return numstr;
	}
	else{
		return grp_ptr->gr_name;
	}
}

void error_handle(const char* name)
{
	perror(name);
}

总结

本次优化了ls显示效果,实现了ls -l。ls -l就是业务多了一点,需要进行多个格式转换处理,算法上无太多磕绊的点,比较通顺。笔者的大部分时间都花费在实现ls的"分栏算法"上,其中的算法逻辑值得研究,而且肯定还有更好的优化算法。看起来很简单的一个ls显示,蕴藏着的技术细节却不少,这就是linux的魅力所在吧。

参考资料

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

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

上一篇:Tesseract-OCR 4.1.0 安装及使用—windows及CentOS【附Java源码实现】


下一篇:Python如何检查文件名是否在UTF8中?