Linux系統程式設計【3.2】——ls命令優化版和ls -l實現

lularible發表於2021-02-10

前情提要

在筆者的上一篇部落格Linux系統程式設計【3.1】——編寫ls命令中,實現了初級版的ls命令,但是與原版ls命令相比,還存在著顯示格式和無顏色標記的不同。經過筆者近兩天的學習,基本解決了這兩個問題,且實現了"ls -l",並對於可選引數"-a"和"-l"有了更好的支援(不管-a,-l輸入順序如何,是"ls -a -l",還是"ls -l -a",還是"ls -al",亦或是"ls -ls",出現位置幾何,重複與否,都能正確執行)。

ls顯示格式的解決

首先,讓我們來觀察一下原版ls顯示的格式:
在這裡插入圖片描述
筆者總結出的原版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,那麼短的字串後面跟的空格數就大大增加。

獲得基準行數之後,預分配基準行數*螢幕寬度的字元數大小,就將字串一一從上到下(排基準行數行),從左到右排列(列數儲存給列數指標),同時對於每一列都獲取最大字串長度(存到每列最大字串長度陣列中),重新計算所佔總字元數時,將每一列最大字串長度乘以行數的值算進去。然後將所佔總字元數減去預分配的大小,繼續分配足夠的行來容納這個差值。

迭代進行上述步驟,直到所佔總字元數不大於預分配的大小。返回最後分配的行數。

如下所示為圖解:
在這裡插入圖片描述
之所以說它是“囫圇吞棗”,是因為在處理超額長度時,直接取最長的那一個代表最後一列的所有字串長度,一刀切了。這可能導致在某些情況下格式顯示不正確。

原始碼如下:

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;
}

在進行輸出時,從左往右,從上到下列印,後續在程式碼中體現。

"精打細算"版分欄演算法

在計算基準行數時,與"囫圇吞棗"的分欄演算法一樣。區別就在於對於超額列的處理上。設定一個當前螢幕剩餘寬度變數,對於每一個字串,當其長度超過剩餘寬度,則終止排列,所需的額外空間大小為剩餘未排列的所有字串塊長度之和。

下圖所示為圖解:
在這裡插入圖片描述
正因為計算超額空間時,算的是每個未排列字串塊長度的真實長度,所以是“精打細算”。並且每次都檢測是否超剩餘寬度,所以能嚴格保證最終顯示格式的正確性。

原始碼如下:

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的對比
在這裡插入圖片描述
前一個是原版ls命令顯示效果,後一個是筆者實現的ls03("囫圇吞棗"版)命令顯示效果,基本一致。

2."精打細算"版分欄演算法和"囫圇吞棗"版分欄演算法比對

之前提到過在某些情況下,"囫圇吞棗"版分欄演算法格式顯示不正確。下圖就是一個例子:

在這裡插入圖片描述
可以看到,"囫圇吞棗(ls03)"顯示格式不對,但"精打細算(ls04)"就沒問題。所以"精打細算"版分欄演算法是"囫圇吞棗"版分欄演算法的優化。

不足之處

目前來看,即使是更厲害的"精打細算"版分欄演算法,也存在兩個主要的不足點。

其一是排序與原版不同,筆者利用的是ASCII值來進行字串排序的,對於大小寫字母,特殊符號和漢字,沒有做到像原版ls那樣合適。

其二是在顯示漢字檔名時,格式不正確,如下所示:
在這裡插入圖片描述
顯示出來的存在沒有左對齊的列。筆者推測,漢字的字串單位長度和顯示出的所佔寬度不是一比一的關係,導致計算時存在偏差。進一步推測,除了漢字,對於其他的特殊符號,只要單位長度和顯示出的所佔寬度不是一比一,都會存在偏差。如果想要解決這個問題,就要查詢這個對應關係,然後進行特定的轉換。

ls顯示顏色的解決

因為要查詢有關顏色方面的資訊,筆者通過linux指導資訊,確定dircolors這個命令可以給我提供有用的資訊,輸入:

man dircolors

顯示資訊如下:
在這裡插入圖片描述
其中有一個選項是:dircolors -p,能夠列印出所有預設的顏色資訊。我們輸入:

dircolors -p > color

">"表示將dircolors -p命令的輸出結果儲存到color檔案中(若沒有則自動建立)。

然後輸入:

vim color

就可以進入檔案檢視其中內容了(當然用"more color"命令也行,只是筆者習慣用"vim"命令),如下所示:
在這裡插入圖片描述
對於每種型別的檔案,都給出了預設的顏色值。
其中關於c語言顏色列印,為了防止本文篇幅過長,影響閱讀體驗,可自行查閱資料,這裡隨便給出一個連結供參考:
printf列印顏色

接下來的事情就是如何獲得指定的檔案型別,到這裡就引出了下一節內容:ls -l的實現。

ls -l的實現

獲取stat結構體內容

首先看一下原版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)才是我們需要的。

在這裡插入圖片描述
將檔案路徑傳入stat函式,可以獲得一個stat型別的結構體,該結構體定義如下:
在這裡插入圖片描述
太棒了,我們要的資訊它都有!

格式轉換

但是不要高興的太早,如果直接列印這個stat結構體裡面的st_mode、st_uid、st_gid和st_mtime,顯示的是一些數字,所以還要對它們一一轉換成字串再輸出。

st_mode位運算

st_mode實際上是一個16位的二進位制數,其結構如下:
在這裡插入圖片描述
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效果展示

在這裡插入圖片描述

原始碼

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的個人部落格,歡迎前去瀏覽。

相關文章