目錄與檔案屬性(ls的編寫)
目錄與檔案屬性(編寫ls)
一、背景
ls可以說是linux命令中最常用的了,它可以顯示一個目錄中的檔案內容。並且ls有許許多多的紛繁複雜的選項。其中-a,-l,-d是經常會見到的。-a選項可以來檢視目錄當中的隱藏檔案,而-l可以用來檢視檔案的詳細資訊,再加上個-d可以檢視該目錄的詳細資訊。這個如此之常用的命令是如何實現的呢,下面就“分門別類”的一步一步來分析一下。
二、分類敘述實現過程
1.ls的實現
要想知道這個ls是如何實現的,那麼首先來看看它“幹”了什麼,這個就很顯而易見了,ls不過是將目錄中的檔案列出來了而已。
知道了這個就可以進行聯機搜尋,看看什麼系統呼叫可以實現將資料夾開啟並且讀出來資料夾中檔名稱這一功能。我是如此來檢索這個系統呼叫的。最使我滿意的很顯然是readdir了(讀一個資料夾),根據以往經驗,要讀資料夾你肯定得首先開啟它。所以我自然聯想到了有可能是用opendir來開啟資料夾,closedir來關閉使用完畢的資料夾。當然,這只是猜測,實際還是來開啟readdir來檢視一下相關的系統呼叫吧。
是這個樣子的,再說,其它的系統呼叫咋看都不像是我需要用的。接著網上翻一翻看看readdir該如何使用。 簡直是大段大段的介紹,簡而言之呢,這個readdir是要從資料夾中讀出來一個叫做dirent的結構體,而這個結構體中包含了我需要得到的一個東西叫做filename也就是檔名了。這段介紹太過複雜,刪繁就簡看下給它一個指向資料夾的指標,它會還你一個結構名叫做dirent的指標,換句話說,這個指標所指向的這個結構中包含了你想要的東西。
當然千萬不可忘掉標頭檔案dirent.h,否則系統都不知道你在說些什麼。
同樣的道理去man一下opendir和closedir的使用方法。這樣下來編出了一個超簡易版的ls。
#include<stdio.h>
#include<dirent.h>
void ls_do(char dirname[]);
int main(int ac,char *av[])
{
if(ac==1)
ls_do(".");
else
while(--ac){
ls_do(*++av);
}
return 0;
}
void ls_do(char dirname[])
{
DIR *dir_p;
struct dirent *direct_p;
if((dir_p=opendir(dirname))==NULL)
fprintf(stderr,"cannot open the file %s\n",dirname);
else
while ((direct_p=(readdir(dir_p)))!=NULL)
printf("%s ",direct_p->d_name);
printf("\n");
close(dir_p);
}
輸出的結果很是難看,不過大致有了ls的功能,當然,除過一個bug,就是系統的ls是不會輸出隱藏檔案的(即以“.”開頭的檔案),只有ls加上-a引數才會全部輸出。
於是加了一些條件就行了。
#include<stdio.h>
#include<dirent.h>
void ls_do(char dirname[]);
void ls_doo(char dname[]);
struct dirent *buf;
int main(int ac,char *av[])
{
if(ac==1)
ls_do(".");
else
if(av[1][0]!='-'||av[1][1]!='a')
while(--ac){
ls_do(*++av);
}
else
if(ac==2)
ls_doo(".") ;
else
{
--ac;
++av;
while(--ac){
ls_doo(*++av);
}
}
return 0;
}
void ls_do(char dirname[])
{
DIR *dir;
if((dir=opendir(dirname))==NULL)
perror(dirname);
else
while((buf=readdir(dir))!=NULL)
if(buf->d_name[0]!='.')
printf("%s ",buf->d_name);
printf("\n");
closedir(dir);
}
void ls_doo(char dname[])
{
DIR *dir;
if((dir=opendir(dname))==NULL)
perror(dname);
else
while((buf=readdir(dir))!=NULL)
printf("%s ",buf->d_name);
printf("\n");
closedir(dir);
}
不過這段程式碼看起來真是繁瑣,可已經有了選項的功能了。
這個是結果的對比。可以看的出來這個功能是有了。
2.ls -l的實現
首先還是看一下它“幹”了什麼。
可以看的出來,ls -l好像還做了不少事情。它不僅僅輸出了目錄中各個檔案的名稱,它還輸出了各個檔案的詳細資訊。第一列:檔案的格式與許可權。第二列:連結數。第三列:所有者名稱。第四列:所屬組名稱。第五列:檔案大小。第六列:檔案最後修改時間。最後列:檔名稱。
同樣使用聯機幫助來查詢可以用的系統呼叫。這裡應該是需要檔案的屬性。Information?status?property?nature?總之這些個都是有可能的。
總之最後找到了stat這個系統呼叫。所以說聯機幫助好用是真的,但是資訊難找也是真的。接下來當然是man一下stat了。
這裡可以明白過來stat的使用方法,也就是把檔名地址給它,它會把狀態資訊的地址給一個結構為stat的結構體的。接著往下看就可以詳細瞭解這個結構體的模樣。
這裡想要的檔案屬性應有盡有。
結合上一小節獲得目錄中的檔名就可以編出來實現ls -l的程式了。
#include<stdio.h>
#include<sys/stat.h>
#include<dirent.h>
void ll_do(char filename[]);
void ls_do(char dirname[]);
int main(int ac,char *av[])
{
if(ac==1)
ls_do(".");
else{
while(--ac)
ls_do(*++av);
}
return 0;
}
void ls_do(char dirname[])
{
DIR *dir_ptr;
struct dirent *buf;
if((dir_ptr=(opendir(dirname)))==NULL)
perror(dirname);
else
while((buf=(readdir(dir_ptr)))!=NULL)
ll_do(buf->d_name);
closedir(dir_ptr);
}
void ll_do(char filename[])
{
struct stat buf;
if(stat(filename,&buf)==-1)
perror(filename);
else
{
printf("%o ",buf.st_mode);
printf("%d ",buf.st_nlink);
printf("%d ",buf.st_uid);
printf("%d ",buf.st_gid);
printf("%d ",buf.st_size);
printf("%d ",buf.st_mtime);
printf("%s\n",filename);
}
}
執行結果如下。
這個和系統中的命令執行出的結果完全是兩個樣子啊。
這個是系統執行出來的樣子。不過經過對比可以發現,其實本質是表達出來同樣的意思了。只不過形式上是難看了點。接下來的工作便是一列一列的對其進行修正,讓它變得好看一些。
1)第一列
對比一下第一列不難發現問題所在。我的程式的檔案格式與許可權是用數字來表示的。而標準的是用字母來表示的。要進行如此轉換需要用到掩碼的概念。為什麼要用掩碼的概念呢?因為我需要看的只是其中的某一位,該位為1就是如何,該位為0就不是如何。具體來說,許可權,我只需看其中的一位是否為1就可知道它的某一使用者(user/group/other)是否具有讀寫或執行的許可權。簡單點來講是把這些八進位制的數作為二進位制與另一串二進位制位相與,將不需要看的位置零,如此可用if語句來判斷之後就更改下字串的字母就完了。 這個在標頭檔案sys/stat.h中有完整的定義,而且名字也是一看就get了,一目瞭然。其它還有很多,我只複製了其中一隅。用S_IRUSR與mode相與並判斷是否為非零,當然即是是否為1,如果是1,那麼就將所有者的讀許可權更改為“r”就好了,如果不是,那就讓字串繼續保持“-”。
2)第二列
無需更改
3)第三列
我的0代表的是root使用者。而這個數字其實就是使用者的ID。將USERID轉換為名稱,這就很容易讓人想到passwd檔案,這檔案其中就包含了USERID和對應的名稱。這裡需要用到一個叫做getpwuid的系統呼叫。man一下它,從中可以看到如下這樣的用法。 顯然,使用這個系統呼叫的方法是,輸出一個uid,它會還給你一個名字叫做passwd的結構體的指標。同樣的套路,往下翻看結構體是什麼面目。東西不少,但我們用的就僅僅是username。到時將結構中的pw_name拿出就是。
4)第四列
和第三列處理方法類似,這裡不再贅述。需要用的是group檔案。對應的系統呼叫時getgrgid。對應的結構體名字叫做group。
5)第五列
無需更改
6)第六列
時間格式這個系統呼叫ctime我先前用過了,這裡也不贅述。就是將數字改為好看的字元的事。
7)第七列
無需更改
這樣子下來,七列全部改完。完整程式碼如下:
#include<grp.h>
#include<pwd.h>
#include<time.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<dirent.h>
#include<string.h>
void ll_do(char filename[]);
void show_username(uid_t uid);
void ls_do(char dirname[]);
void show_time(long);
void show_groupname(int gid);
void show_mode(int mode);
int main(int ac,char *av[])
{
if(ac==1)
ls_do(".");
else{
while(--ac)
ls_do(*++av);
}
return 0;
}
void ls_do(char dirname[])
{
DIR *dir_ptr;
struct dirent *buf;
if((dir_ptr=(opendir(dirname)))==NULL)
perror(dirname);
else
while((buf=(readdir(dir_ptr)))!=NULL)
ll_do(buf->d_name);
closedir(dir_ptr);
}
void ll_do(char filename[])
{
struct stat buf;
if(stat(filename,&buf)==-1)
perror(filename);
else
{
show_mode(buf.st_mode);
printf("%d ",buf.st_nlink);
show_username(buf.st_uid);
show_groupname(buf.st_gid);
printf("%d ",buf.st_size);
show_time(buf.st_mtime);
printf("%s\n",filename);
}
}
void show_time(long timeval)
{
char *cp;
cp=ctime(&timeval);
printf("%12.12s ",cp+4);
}
void show_mode(int mode)
{
char str[10];
strcpy(str,"----------");
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';
printf("%s ",str);
}
void show_username(uid_t uid)
{
struct passwd *buf;
buf=getpwuid(uid);
printf("%s ",buf->pw_name);
}
void show_groupname(int gid)
{
struct group *buf;
buf=getgrgid(gid);
printf("%s ",buf->gr_name);
}
執行結果如下:
跟標準的還是挺像的。不過還是有一些問題:1.這裡沒把隱藏檔案去掉。得用if語句去掉,這裡就不贅述了。2.這個命令不可以指定資料夾。這個就是個大缺憾了,問題正在修補中。
3.ls -l -d的實現
這個是要比ls -l簡單些的,只是將上一節中的輸入目錄檔案的名稱改為輸入目錄的名稱。而且這個結果比較理想,沒有上一節的那兩個問題。
三、總結
這個命令介紹的比較詳細,主要是其中涉及了很多沒有用過的系統呼叫。這個系統的編寫讓我感觸很深的是結構體的使用,結構體到處都是無處不在,而讀資訊用資訊都是從結構體中來的。並且大多用的是指標。