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