知识要点
1.Linux系统编程 目录和文件部分
2.C语言编程能力
任务说明
ls 是 GNU/Linux 操作系统中常见的命令. 请使用 C 语言,基于 GNU/Linux 操作系统,编程实现 ls 命令的 部分 功能.
验收要求
- 实现 ls 的 -a、-l、-R、-t、-r、-i、-s 参数,并允许这些参数任意组合.
- -R 需要通对 / 的遍历测试
- 界面美观(输出对齐、与颜色显示等)
- 无资源与内存泄露
参考资料
- man 手册.
- MichaelKerrisk.Linux/UNIX系统编程手册[M].北京:人民邮电出版社.
- W.RichardStevens.Stephen.UNIX环境高级编程[M].第3版.戚正伟,译.北京:人民邮电出版社.
- Unix/Linux编程实践教程
所要用到的函数
struct dirent,来自#include,diret是结构体中成员d_name用于存放文件名
struct dirent { ino_t d_ino; off_t d_off; unsigned short d_reclen; char d_name[1];
- opendir:打开目录文件
函数返回指向目录流的指针。出错时,将返回 NULL,并正确设置 errno
readdir:读取目录条目,返回
closedir:关闭目录文件
stat: 得到文件信息,是在#include库里面的,
函数原型: int result =stat(char*fname,struct stat *bufp)
参数: fname 文件名 , bufp 指向buffer的指针
返回值: 遇到错误返回 -1,成功返回 0
ls初步简单实现(较为简单直接上代码)
#include
#include
#include
#include
#define MAX_LEN 150
int g_leavelen = MAX_LEN;
int g_maxlen;
void do_ls(char []);
int main(int argc,char *argv[])
{
if(argc==1)
{
do_ls("."); // .代表当前目录
}
else
{
while(--argc)
{
printf("%s:\n",*++argv);
do_ls(*argv);
}
}
return 0;
}
void do_ls(char dirname[])
{
DIR*dir_ptr;
struct dirent*direntp;
if((dir_ptr=opendir(dirname))==NULL) //打开失败
{
fprintf(stderr,"lsl:cannot open %s\n",dirname);
}
else //打开成功
{
while((direntp=readdir(dir_ptr))!=NULL) //依次读取目录,找到最大长度
{
if(direntp->d_name[0]!='.')
{
printf("%s ",direntp->d_name);
}
}
closedir(dir_ptr);
}
ls -l的初步实现
所要的效果
分析以及所需要解决的问题
需要显示文件类型和许可权限,文件链接数,用户ID,所属组ID,所占空间大小,文件修改时间,文件名称。
我们要获取这些信息就需要,stat的联机帮助和/usr/include/sys/stat.h。
他们描述了struct stat 的成员变量
先简单使用一下,代码如下:
#include
#include
#include
void show_stat_info(char*fname,struct stat*buf);
int main(int argc,char*argv[])
{
struct stat info;
if(argc>1)
if(stat(argv[1],&info)!=-1)
{
show_stat_info(argv[1],&info);
return 0;
}
else
perror(argv[1]);
return 1;
}
void show_stat_info(char*fname,struct stat*buf)
{
printf(" mode : %o\n",buf->st_mode);
printf(" links: %d\n",buf->st_nlink);
printf(" user : %d\n",buf->st_uid);
printf(" group: %d\n",buf->st_gid);
printf(" size : %d\n",buf->st_size);
printf(" modtime : %d\n",buf->st_mtim);
printf(" name: %s\n",fname);
}
在终端执行如下:
系统ls -l my_ls2.c 的结果如下:
我们用stat调出来的
- 文件类型和许可权限是一串八进制代码
- 用户ID和所属组ID都是一串数字
- 显示的时间是时间戳
以上就是我们要实现 ls -l所需要解决的问题
将模式字段转换成字符
s_mode中的这串模式字段是一串八进制数字
把它传化为一个16位的二进制数
通过掩码把其他无关部分置为0,再与表示目录的代码比较,从而判断这是否是一个目录
更简单的方法就是用中的宏来代替上述代码:
根据上述,我们已经可以解码获得许可权限,代码如下:
void mode_to_letters(int mode,char str[])
{
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';
}
将用户,所属组ID转化为字符串
我们可以通过库函数getpwuid来访问用户信息,如果用户信息保存在etc/passwd中,那么
getpwuid会查找/etc/passwd的内容,如果用户信息在NIS中,getpwuid会从NIS中获取信息。
getpeuid需要UID(user ID)作为参数,返回一个指向struct passwd的指针,这个结构定义在
/usr/include/pwd.h中如下:
所属组也有一个对应得结构体group和passwd类似
现在就可以实现将用户,所属组ID转化为字符串了
代码如下:
109 char*uid_to_name(gid_t uid)
110 {
W>111 struct passwd*getpwuid();
112 struct passwd*pw_ptr;
113 static char numstr[10];
E>114 if((pw_ptr=getpwuid(uid))==NULL)
115 {
116 sprintf(numstr,"%d",uid);
117 return numstr;
118 }
119 else
120 {
121 return pw_ptr->pw_name;
122 }
123 }
124 char*gid_to_name(gid_t gid)
125 {
W>126 struct group*getgrgid(),*grp_ptr;
127 static char numstr[10];
E>128 if((grp_ptr=getgrgid(gid))==NULL)
129 {
130 sprintf(numstr,"%d",gid);
131 return numstr;
132 }
133 else
134 {
135 return grp_ptr->gr_name;
136 }
最终ls -l代码实现以及效果
#include
#include
#include
#include
#include
#include
#include
void do_ls(char[]); //依次提取文件
void dostat(char*);
void show_file_info(char*,struct stat*); //打印文件信息
void mode_to_letters(int ,char[]);
char*uid_to_name(uid_t); //将用户ID转化为字符串
char*gid_to_name(gid_t); //将所属组ID转化为字符串
int main(int argc,char* argv[])
{
if(argc==1)
do_ls("."); //进入当前目录
else //若有参数,进入指定目录
{
while(--argc)
{
printf("%s:\n",*++argv);
do_ls(*argv);
}
}
return 0;
}
void do_ls(char dirname[])
{
DIR*dir_ptr;
struct dirent*direntp;
if((dir_ptr=opendir(dirname))==NULL)
fprintf(stderr,"lsl:cannot open %s\n",dirname);
else
{
while((direntp=readdir(dir_ptr))!=NULL)
{
if(direntp->d_name[0]!='.') //判断是否是隐藏文件,若不是执行下一步
dostat(direntp->d_name);
}
}
closedir(dir_ptr);
}
}
void dostat(char*filename)
{
struct stat info;
if(stat(filename,&info)==-1)
perror(filename);
else
show_file_info(filename,&info);
}
void show_file_info(char*filename,struct stat*info_p)
{
char*uid_to_name(),*ctime(),*git_to_name(),*filemode();
void mode_to_letters();
char modestr[11];
mode_to_letters(info_p->st_mode,modestr);
printf("%s ",modestr);
printf("%4d ",(int)info_p->st_nlink);
printf("%-8s ",uid_to_name(info_p->st_uid));
printf("%-8s ",gid_to_name(info_p->st_gid));
printf("%8ld ",(long)info_p->st_size);
printf("%.12s ",4+ctime(&info_p->st_size));
printf("%s\n",filename);
}
void mode_to_letters(int mode,char str[])
{
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';
}
char*uid_to_name(gid_t uid)
{
struct passwd*getpwuid();
struct passwd*pw_ptr;
static char numstr[10];
if((pw_ptr=getpwuid(uid))==NULL)
{
sprintf(numstr,"%d",uid);
return numstr;
}
else
{
return pw_ptr->pw_name;
}
}
char*gid_to_name(gid_t gid)
{
struct group*getgrgid(),*grp_ptr;
static char numstr[10];
if((grp_ptr=getgrgid(gid))==NULL)
{
sprintf(numstr,"%d",gid);
return numstr;
}
else
{
return grp_ptr->gr_name;
}
}
效果展示:
ls -a(显示隐藏文件)
这一步骤实现起来很简单,隐藏文件就是文件名第一个字符是 . 的文件 比如 .git
我们在普通的ls 显示时都会有
if(direntp->d_name[0]=='.')
continue;
此步骤就是为了不显示隐藏文件
我们可以在执行到这一步时,对参数进行判断,查看是否有参数 -a 若有该参数跳过该步骤即可
ls -s(在文件左侧显示文件大小,以1024字节为块单位)
此步骤也极为简单,在结构体stat中存有该文件的所占字节的大小,st_size
我们只需要对该文件字节大小除以1024就可以得到所需显示的文件大小
但需要注意的是,当除以1024后得到的大小小于等于4且不等于0时,也需要显示4
实现起来也很简单,当检测到 -s 参数时执行下面代码即可:
struct stat info;
if(stat(filename,&info)==-1)
perror(filename);
long long size=info.st_size/1024;
if(size<=4)
printf("4 ");
else
printf("%-4lld",size);
ls -i(输出文件的 i 节点的索引信息)
文件的索引信息也是保存在结构体stat中的st_ino中的,当检测到参数 -i 执行下面即可
struct stat info;
if(stat(filenames[j],&info)==-1)
perror(filenames[j]);
printf("%d ",info.st_ino);
ls -R(递归遍历目录)
思路讲解
我们可以先将该目录下的所有文件依次遍历打印出来,当打印完后,再次对该文件进行依次遍历,在此次遍历的过程中要对其文件的类型进行判断,若判断此文件为目录文件时,需要将此目录文件再次传入该函数,进行递归。
注意事项,当判断该文件为目录文件时,若要想在递归进入的函数中访问到该目录下的文件信息,我们应当传入该目录文件的路径,而不是目录名,此时我们就可以用到函数
sprintf
#include
#include
#include
#include
#include
void ls_R(char path[]);
int main()
{
ls_R("/");
return 0;
}
void ls_R(char path[])
{
printf("%s:\n",path);
DIR*dir_ptr;
struct dirent*direntp;
if((dir_ptr=opendir(path))==NULL)//打开目录
fprintf(stderr,"lsl:cannot open %s\n",path);
else
{
if(direntp->d_name[0]=='.')
continue;
while((direntp=readdir(dir_ptr))!=NULL)//读取当前目录文件
{
printf("%s ",direntp->d_name);
}
}
printf("\n");
closedir(dir_ptr);
if((dir_ptr=opendir(path))==NULL)//打开目录
fprintf(stderr,"lsl:cannot open %s\n",path);
else
{
while((direntp=readdir(dir_ptr))!=NULL)
{
if(direntp->d_name[0]=='.')
continue;
struct stat info;
char temp[PATH_MAX];
sprintf(temp,"%s/%s",path,direntp->d_name);
if(lstat(temp,&info)==-1)
perror(temp);
if(S_ISDIR(info.st_mode))//判断是否为目录,如果是目录就进入递归
{
ls_R(temp);
}
}
}
}