目录与文件属性(编写ls)
一、背景
ls可以说是linux命令中最常用的了,它可以显示一个目录中的文件内容。并且ls有许许多多的纷繁复杂的选项。其中-a,-l,-d是经常会见到的。-a选项可以来查看目录当中的隐藏文件,而-l可以用来查看文件的详细信息,再加上个-d可以查看该目录的详细信息。这个如此之常用的命令是如何实现的呢,下面就“分门别类”的一步一步来分析一下。
二、分类叙述实现过程
1.ls的实现
要想知道这个ls是如何实现的,那么首先来看看它“干”了什么,这个就很显而易见了,ls不过是将目录中的文件列出来了而已。
我是如此来检索这个系统调用的。最使我满意的很显然是readdir了(读一个文件夹),根据以往经验,要读文件夹你肯定得首先打开它。所以我自然联想到了有可能是用opendir来打开文件夹,closedir来关闭使用完毕的文件夹。当然,这只是猜测,实际还是来打开readdir来查看一下相关的系统调用吧。
给它一个指向文件夹的指针,它会还你一个结构名叫做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)第一列
其它还有很多,我只复制了其中一隅。用S_IRUSR与mode相与并判断是否为非零,当然即是是否为1,如果是1,那么就将所有者的读权限更改为“r”就好了,如果不是,那就让字符串继续保持“-”。
2)第二列
无需更改
3)第三列
东西不少,但我们用的就仅仅是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简单些的,只是将上一节中的输入目录文件的名称改为输入目录的名称。而且这个结果比较理想,没有上一节的那两个问题。
三、总结
这个命令介绍的比较详细,主要是其中涉及了很多没有用过的系统调用。这个系统的编写让我感触很深的是结构体的使用,结构体到处都是无处不在,而读信息用信息都是从结构体中来的。并且大多用的是指针。