前情提要
在筆者的上一篇部落格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的個人部落格,歡迎前去瀏覽。