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


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM