ls命令簡介
老規矩,直接在終端輸入:man ls
(有關於man命令的簡介可以參考筆者前期部落格:Linux系統程式設計【1】——編寫more命令)
可以看到,ls命令的作用是顯示目錄中的檔名,它帶有可選的引數,如'-a'表示顯示所有檔案(包含隱藏檔案,即以'.'開頭的檔案),'-l'表示顯示檔案及檔案屬性等等。
本次部落格就只專注於如何顯示出目錄中的檔名,而顯示檔案屬性這方面的實現將寫在下一篇部落格中。
如何實現初級版ls命令
既然我們的目的是要顯示出目錄中的檔案,基於Linux檔案程式設計的思想,我們只需找到存放指定目錄檔案資訊的那個檔案,然後讀取其中的內容並顯示就可以了。
根據之前對於more和who命令的實現中:開啟檔案、讀取檔案、關閉檔案的思路,可以猜測對於目錄的處理也可能為:開啟目錄、讀取目錄、關閉目錄。
確定工具函式
利用man -k dir查詢到readdir(3)就是我們想要的(另外的兩個readdir(2)和readdir_r(3) man進去看一下描述,確實不是我們需要的)。
由readdir的“SEE ALSO”引出來的還有,opendir和closedir:
這裡插一句,我們之前的思路是找到儲存目錄資訊的檔案,然後對這個檔案內容進行處理。但是筆者發現這些檔案不容易找到,好在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);
}
}
效果展示
與原版ls命令不同點
雖然已經基本實現了ls命令,但是與原版ls相比,還存在一些不同,比如輸出的格式是一行一個檔名,原版的為一行多個且對齊,還有就是原版ls顯示的不同檔案帶有不同的顏色。這個估計和檔案型別有關,待下一篇部落格仔細研究。
參考資料
《Understanding Unix/Linux Programming A Guide to Theory and Practice》
歡迎大家轉載本人的部落格(需註明出處),本人另外還有一個個人部落格網站:[https://www.lularible.cn],歡迎前去瀏覽。