Linux系統程式設計【3.1】——編寫ls命令

lularible發表於2021-02-07

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],歡迎前去瀏覽。

相關文章