主要功能
- 实现
-l , -a , -R
; - 实现三种参数的组合调用;
- 在任何目录下都可以使用自己的
ls
命令; - 自己的命令和系统的
ls
命令重定向输入到文件中去大小相差不能太多,1.25.倍以内;
实现过程
- 一些重要的函数
lstat()/stat()/fstat()
函数解析;opendir()
函数;readdir()
函数;
- -l的实现-al的实现
- -R与-Rl的递归调用
- 链接文件的解析
1.1 lstat()/stat()/fstat()函数解析
首先我们来看一下三个函数的定义:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *file_name , struct stat *buf);
int fstat(int filedes , struct stat *buf);
int lstat(const char *file_name,struct stat *buf)
当函数执行成功时都返回0,当有错误发生时返回-1,错误代码存放在errno
中;
区别:
- 获取文件的状态信息的方式不同:
stat
和lstat
是通过文件名获取文件信息的,而fstat
是通过文件描述符获取文件信息的; - 对于链接文件返回的状态信息不同:
stat
和fstat
返回的是符号链接指向的文件状态信息,而lstat
返回的是符号链接文件本身的信息;
关于struct stat
结构体:
struct stat{
dev_t st_dev; //文件的设备编号
ino_t st_ino; //文件的inode
mode_t st_mode;//文件的类型和权限
nlink_t st_nlink; //链接到该文件的硬链接数目
uid_t st_uid; //文件所有者的用户id
gid_t st_gid; //文件所有者的组id
dev_t st_rdev; //若文件为设备文件则为设备编号
off_t st_size; //文件大小以字节计算,如果是符号链接则文指向文件的文件名长度
blksize_t st_blksize; //文件系统的I/O缓冲区大小
blkcmt_t stblocks; //占用文件区块数
time_t st_atime; //文件最后一次被访问的时间
time_t st_mtime; //文件最后一次被修改的时间,一般调用utime和write函数时才改变
time_t st_ctime; //文件最近一次被更改的时间,此参数在文件所有者所属组文件权限被更改时更新
}
1.1.1关于struct stat结构体里参数的用法
对于-l
命令来说一般需要用到st_ctime , st_uid , st_gid , st_mode
对于文件类型以及权限的判断如下:
//判断文件类型
if(S_ISLNK(buf.st_mode)) //判断是否为链接
printf("l");
if(S_ISREG(buf.st_mode)) //判断是否为一般文件
printf("-");
if(S_ISDIR(buf.st_mode)) //判断是否为目录
printf("d");
if(S_ISCHR(buf.st_mode)) //字符设备文件
printf("c");
if(S_ISBLK(buf.st_mode)) //块设备文件
printf("b");
if(S_ISFIFO(buf.st_mode)) //先进先出FIFO
printf("f");
if(S_ISSOCK(buf.st_mode)) //socket
printf("s");
//判断权限
if((buf.st_mode & S_IRUSR))
printf("r");
else
printf("-");
if((buf.st_mode & S_IWUSR))
printf("w");
else
printf("-");
if((buf.st_mode & S_IXUSR))
printf("x");
else
printf("-");
if((buf.st_mode & S_IRGRP))
printf("r");
else
printf("-");
if((buf.st_mode & S_IWGRP))
printf("w");
else
printf("-");
if((buf.st_mode & S_IXGRP))
printf("x");
else
printf("-");
if((buf.st_mode & S_IROTH))
printf("r");
else
printf("-");
if((buf.st_mode & S_IWOTH))
printf("w");
else
printf("-");
if((buf.st_mode & S_IXOTH))
printf("x");
else
printf("-");
对于文件所有者,和所属组有这两个结构体struct passwd 和 struct group
这两个结构体里面存放着用户名和组名,只需要调用getpwuid() 和 getgrgid()
这两个函数就可以了,这两个函数存放在#include <pwd.h> 和 #include <grp.h>
头文件下,具体代码如下:
pwd = getpwuid(buf.st_uid);
grp = getgrgid(buf.st_gid);
printf("%-8s",pwd->pw_name);
printf("%-8s",grp->gr_name);
对于最后一次修改时间mtime,在#inlcude <time.h>
头文件中有一个ctime()
用来解析时间:
//打印文件最后一次修改的时间
strcpy(buff,ctime(&buf.st_ctime));
buff[strlen(buff)-1] = '\0';
printf(" %s",buff);
1.2opendir()函数
#include <sys.types.h>
#include <dirent.h>
DIR *opendir(const char *name)
使用opendir()
函数打开参数name
指定的目录,并返回DIR*形态的目录流,类似于文件操作的文件描述符,接下来对于目录的读取和搜索都要用到这个返回值,失败则返回NULL,错误代码存入erron
中;
1.3readdir()函数
在我们实现-l
操作的时候打开目录读取目录中的文件离不开opendir() 和 readdir()
这两个函数;
#include <sys/types.h>
#include <dirent.h>
struct dirent * readdir(DIR *dir)
其中struct dirent
的定义如下:
struct dirent
{
long d_ino; //代表此目录i结点编号
off_t d_off; //指目录文件开头至此目录进入点的位移
unsigned short d_reclen; //是指d_name的长度
char d_name[NAME_MAX+1]; //是指以NULL结尾的文件名
}
函数执行成功返回该目录的下一个文件的信息;
假如用opendir
打开一个目录,第一次调用该函数返回第一个文件的信息,第二次返回第二个,以此类推直到返回NULL;
所以我们通过此函数获取文件名
2.1-l与-al的实现
2.1.1-l
-l
的实现就在于先确定文件的类型,分为目录文件和其他,如果是目录文件我们就使用opendir() 和 readdir()
这两个函数来获取目录里面的文件信息(主要为文件名),其中要注意的是获取了文件名以后使用lstat()
函数传递参数时要传递文件的绝对路径,这里有两种方法来获取文件的绝对路径:
- 使用
sprintf()
函数对readdir()
函数返回的结构体struct dirent
中的文件名d_name
与打开的目录进行字符串的拼接来获得文件的绝对路径 - 就是通过改变当前的工作工作目录,通过调用
chdir()
函数
#include <unistd.h>
int chdir(const char *path)
chdir()
将当前工作目录改为由参数path
指定的目录;但是使用这个函数时只能改变当前进程的工作目录,并不能改变父进程的工作目录,这个问题在这里不做讨论,会在<<my_shell的实现>>这篇博客里面实现.
2.1.2关于-a
-a
就是打印出隐藏目录,区别隐藏目录和非隐藏目录就是根据文件名的第一个字符是不是'.'
,然后设置标志位判断有没有-a
参数,但是要注意当前目录和上一级目录. 和 ..
目录的第一个字符也是点,所以要加一个字符串匹配函数strcmp
来判断到底是隐藏目录还是. 和 ..
;
2.1.3具体代码
void have2(char *path)
{
DIR *dir;
int i = 0;
int number = 0;
DIR *dir2;
struct stat buf;
char a[10000][256];
struct dirent *ptr;
dir = opendir(path);
char b[10];
if(dir == NULL)
my_err("opendir",__LINE__);
chdir(path);
while((ptr = readdir(dir))!=NULL)
{
if(strcmp(ptr->d_name,"4523") ==0)
continue;
if(opendir(ptr->d_name) < 0)
continue;
if(ptr->d_name[0] == '.' && biaozhi == 0 && strcmp(ptr->d_name,"..") !=0 && strcmp(ptr->d_name,".") != 0)
continue;
if(lstat(ptr->d_name,&buf)<0)
continue;
strcpy(a[i],ptr->d_name);
a[i][strlen(ptr->d_name)+1] = '\0';
i++;
number++;
}
for(i=0;i<number;i++)
{
if(lstat(a[i],&buf)<0)
continue;
if(S_ISDIR(buf.st_mode))
{
if((dir2 = opendir(a[i])) < 0)
continue;
}
print_file(buf,a[i]);
}
}
2.1.4一些注意的地方
我上面有一个二维数组开的很大在代码的第八行char a[10000][256]
,为什么呢?
因为在你的系统里有一些目录里面的文件实在是太多了,这里我们也没有选择链表或者动态数组的方法,所以我们把数组开的比较大.建议大家在实现了一些基本功能以后试一下my_ls -l /usr/bin出现段错误的时候你就明白我说的意思了;
还有一个问题我们要说就是有的目录打不开进不去的问题,例如/proc目录在-R,-l时都有可能会出现段错误,这个时候对于一些打不开的文件跳过就好了,因为我也没有办法.
3.1-R的-Rl的递归实现
这个功能要求遍历所有的目录,要求要遍历根目录;
这个问题肯定是要用递归来实现的,当然链表也是必不可少的,如果你只开了一个数组,那么很快这个数组就会塞满很快你就会看到熟悉的段错误
struct rrr{
char a[256];
struct rrr *next;
};
void LR(struct rrr *head)
{
DIR *dir;
int k;
DIR *dir2;
struct stat buf;
char arr[256];
struct dirent *ptr;
struct rrr *p = head;
struct rrr *p1,*phead = NULL,*p2;
while(p != NULL)
{
printf("\n%s:\n",p->a);
if((dir = opendir(p->a)) == NULL)
{
p = p->next;
continue;
}
while((ptr = readdir(dir)) != NULL)
{
if(strcmp(ptr->d_name,".")==0)
continue;
if(strcmp(ptr->d_name,"..") == 0)
continue;
if(ptr->d_name[0] == '.')
continue;
strcpy(arr,ptr->d_name);
sprintf(arr,"%s%s",p->a,ptr->d_name);
if(lstat(arr,&buf) < 0)
continue;
if(buf.st_uid != 0 && buf.st_uid != 1000)
continue;
pwd = getpwuid(buf.st_uid);
if(strcmp(pwd->pw_name,"root")!=0 && strcmp(pwd->pw_name,"xzwb")!=0)
continue;
print_file(buf,arr);
if(S_ISDIR(buf.st_mode))
{
k = strlen(arr);
arr[k] = '/';
arr[k+1] = '\0';
p1 = (struct rrr*)malloc(sizeof(struct rrr));
strcpy(p1->a,arr);
if(phead == NULL)
phead = p1;
else
p2->next = p1;
p2 = p1;
}
}
closedir(dir);
if(phead == NULL)
{
p = p->next;
continue;
}
p2->next = NULL;
printf("\n");
LR(phead);
free(phead);
phead = NULL;
p = p->next;
}
}
这是-Rl的实现-R和这个差不多就在下面放代码;
3.1.1代码分析
- 关于倒数第14行的
closedir(dir)
是因为在一个程序最多只能打开256个文件,所以我们必须要在遍历完这个目录后就关掉他. - 关于链表,使用一个新的链表来存放当前目录里面的目录名,通过
sprint()
函数实现获取目录的绝对路径;
4.链接文件的处理
我们通过readlink
这个函数来处理链接文件
char * get_exe_path( char *path ,char * buf, int count)
{
int i;
int rslt = readlink(path, buf, count - 1);
if (rslt < 0 || (rslt >= count - 1))
{
return NULL;
}
buf[rslt] = '\0';
for (i = rslt; i >= 0; i--)
{
if (buf[i] == '/')
{
buf[i + 1] = '\0';
break;
}
}
return buf;
}
关于readlink
函数:
#include <unistd.h>
int readlink(const char *path, char *buf, size_t bufsiz);
readlink()会将参数path的符号连接内容到参数buf所指的内存空间,返回的内容不是以NULL作字符串结尾,但会将字符串的字符数返回。若参数bufsiz小于符号连接的内容长度,过长的内容会被截断
完整代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <sys/wait.h>
int biaozhi = 0;
struct rrr{
char a[256];
struct rrr *next;
};
char * get_exe_path( char *path ,char * buf, int count)
{
int i;
int rslt = readlink(path, buf, count - 1);
if (rslt < 0 || (rslt >= count - 1))
{
return NULL;
}
buf[rslt] = '\0';
for (i = rslt; i >= 0; i--)
{
if (buf[i] == '/')
{
buf[i + 1] = '\0';
break;
}
}
return buf;
}
int pdd=0;
//自定义的错误处理函数
void my_err(const char *err_string , int line)
{
fprintf(stderr,"line:%d",line);
perror(err_string);
exit(1);
}
//打印文件列表的一行
int print_file(struct stat buf ,char *name)
{
char buff[64];
char hen[1024];
struct passwd *pwd; //从该结构体获取文件所有者的名字
struct group *grp; //从该结构体获取文件组名
//判断文件类型
if(S_ISLNK(buf.st_mode)) //判断是否为链接
printf("l");
if(S_ISREG(buf.st_mode)) //判断是否为一般文件
printf("-");
if(S_ISDIR(buf.st_mode)) //判断是否为目录
printf("d");
if(S_ISCHR(buf.st_mode)) //字符设备文件
printf("c");
if(S_ISBLK(buf.st_mode)) //块设备文件
printf("b");
if(S_ISFIFO(buf.st_mode)) //先进先出FIFO
printf("f");
if(S_ISSOCK(buf.st_mode)) //socket
printf("s");
//判断权限
if((buf.st_mode & S_IRUSR))
printf("r");
else
printf("-");
if((buf.st_mode & S_IWUSR))
printf("w");
else
printf("-");
if((buf.st_mode & S_IXUSR))
printf("x");
else
printf("-");
if((buf.st_mode & S_IRGRP))
printf("r");
else
printf("-");
if((buf.st_mode & S_IWGRP))
printf("w");
else
printf("-");
if((buf.st_mode & S_IXGRP))
printf("x");
else
printf("-");
if((buf.st_mode & S_IROTH))
printf("r");
else
printf("-");
if((buf.st_mode & S_IWOTH))
printf("w");
else
printf("-");
if((buf.st_mode & S_IXOTH))
printf("x");
else
printf("-");
//打印链接数
printf(" %4d ",buf.st_nlink);
//打印文件用户和用户组
pwd = getpwuid(buf.st_uid);
grp = getgrgid(buf.st_gid);
printf("%-8s",pwd->pw_name);
printf("%-8s",grp->gr_name);
//打印文件大小
printf("%8d",buf.st_size);
//打印文件最后一次修改的时间
strcpy(buff,ctime(&buf.st_ctime));
buff[strlen(buff)-1] = '\0';
printf(" %s",buff);
if(S_ISLNK(buf.st_mode))
{
printf(" %s->",name);
printf("%s\n",get_exe_path(name,(char*)hen,1024));
}
else
printf(" %s\n",name);
return 0;
}
//只打印文件名的
void printf_name(char name[][256],int num)
{
int i,j=0;
for(i=0;i<num;i++)
{
j++;
printf("%-20s",name[i]);
if(j==5)
{
printf("\n");
j = 0;
}
}
printf("\n");
}
//从目录中获得文件列表 ls -a 和 ls
void have(char *path)
{
DIR *dir;
int i = 0;
int number = 0;
char a[10000][256];
struct dirent *ptr;
dir = opendir(path);
if(dir == NULL)
my_err("opendir",__LINE__);
while((ptr = readdir(dir))!=NULL)
{
if(ptr < 0)
continue;
if(ptr->d_name[0] == '.' && biaozhi == 0);
continue;
strcpy(a[i],ptr->d_name);
a[i][strlen(a[i])+1] = '\0';
i++;
number++;
}
printf_name(a,number);
}
//ls -l 和 ls -al
void have2(char *path)
{
DIR *dir;
int i = 0;
int number = 0;
DIR *dir2;
struct stat buf;
char a[10000][256];
struct dirent *ptr;
dir = opendir(path);
char b[10];
if(dir == NULL)
my_err("opendir",__LINE__);
chdir(path);
while((ptr = readdir(dir))!=NULL)
{
if(strcmp(ptr->d_name,"4523") ==0)
continue;
if(opendir(ptr->d_name) < 0)
continue;
if(ptr->d_name[0] == '.' && biaozhi == 0)
continue;
if(lstat(ptr->d_name,&buf)<0)
continue;
strcpy(a[i],ptr->d_name);
a[i][strlen(ptr->d_name)+1] = '\0';
i++;
number++;
}
// chdir(path);
for(i=0;i<number;i++)
{
if(lstat(a[i],&buf)<0)
continue;
if(S_ISDIR(buf.st_mode))
{
if((dir2 = opendir(a[i])) < 0)
continue;
}
print_file(buf,a[i]);
}
}
//实现ls -R
void R(struct rrr *head)
{
DIR *dir;
int k;
int haha = 0;
struct stat buf;
char arr[256];
struct dirent *ptr;
struct rrr *p = head;
struct rrr *p1,*phead = NULL,*p2;
while(p != NULL)
{
printf("\n%s:\n",p->a);
if((dir = opendir(p->a)) == NULL)
{
p = p->next;
continue;
}
// dir = opendir(p->a);
while((ptr = readdir(dir)) != NULL)
{
if(strcmp(ptr->d_name,".")==0)
continue;
if(strcmp(ptr->d_name,"..") == 0)
continue;
if(ptr->d_name[0] == '.')
continue;
strcpy(arr,ptr->d_name);
printf("%-15s",arr);
haha += 1;
if(haha == 4)
{
printf("\n");
haha = 0;
}
sprintf(arr,"%s%s",p->a,ptr->d_name);
lstat(arr,&buf);
if(S_ISDIR(buf.st_mode))
{
k = strlen(arr);
arr[k] = '/';
arr[k+1] = '\0';
p1 = (struct rrr*)malloc(sizeof(struct rrr));
strcpy(p1->a,arr);
if(phead == NULL)
phead = p1;
else
p2->next = p1;
p2 = p1;
}
}
closedir(dir);
if(phead == NULL)
{
p = p->next;
continue;
}
p2->next = NULL;
printf("\n");
R(phead);
free(phead);
phead = NULL;
p = p->next;
}
}
//实现ls -lR
void LR(struct rrr *head)
{
DIR *dir;
int k;
DIR *dir2;
struct passwd *pwd; //从该结构体获取文件所有者的名字
struct group *grp; //从该结构体获取文件组名
struct stat buf;
char arr[256];
struct dirent *ptr;
struct rrr *p = head;
struct rrr *p1,*phead = NULL,*p2;
while(p != NULL)
{
printf("\n%s:\n",p->a);
if((dir = opendir(p->a)) == NULL)
{
p = p->next;
continue;
}
while((ptr = readdir(dir)) != NULL)
{
if(strcmp(ptr->d_name,".")==0)
continue;
if(strcmp(ptr->d_name,"..") == 0)
continue;
if(strcmp(ptr->d_name,"proc")==0)
continue;
if(strcmp(ptr->d_name,"mimetypes") == 0)
continue;
if(ptr->d_name[0] == '.')
continue;
if(strcmp(ptr->d_name,"OS")==0)
continue;
strcpy(arr,ptr->d_name);
sprintf(arr,"%s%s",p->a,ptr->d_name);
if(lstat(arr,&buf) < 0)
continue;
if(buf.st_uid != 0 && buf.st_uid != 1000)
continue;
pwd = getpwuid(buf.st_uid);
if(strcmp(pwd->pw_name,"root")!=0 && strcmp(pwd->pw_name,"xzwb")!=0)
continue;
print_file(buf,arr);
if(S_ISDIR(buf.st_mode))
{
k = strlen(arr);
arr[k] = '/';
arr[k+1] = '\0';
p1 = (struct rrr*)malloc(sizeof(struct rrr));
strcpy(p1->a,arr);
if(phead == NULL)
phead = p1;
else
p2->next = p1;
p2 = p1;
}
}
closedir(dir);
if(phead == NULL)
{
p = p->next;
continue;
}
p2->next = NULL;
printf("\n");
LR(phead);
free(phead);
phead = NULL;
p = p->next;
}
}
int main(int argc,char *argv[])
{
char path[32];
struct rrr *head,*p;
int choose=0;
int i,j,k,m=0,num=0,c;
struct stat buf;
char *hen[256];
int pid;
if(argc==1)
{
strcpy(path,"./");
have(path);
return 0;
}
for(i=1;i<argc;i++)
{
if(argv[i][0] == '-')
{
k = strlen(argv[i]);
for(j=0;j<k;j++)
{
if(argv[i][j] == 'a')
choose += 1;
if(argv[i][j] == 'l')
choose += 2;
if(argv[i][j] == 'R')
choose += 4;
}
}
else
{
strcpy(path,argv[i]);
num++;
}
}
i--;
if(num != 0)
{
if(stat(path,&buf) == -1)
my_err("path",__LINE__);
if(S_ISDIR(buf.st_mode))
{
p = (struct rrr*)malloc(sizeof(struct rrr));
head = p;
strcpy(p->a,path);
p->next = NULL;
if(path[strlen(argv[i])-1] != '/')
{
path[strlen(argv[i])] = '/';
path[strlen(argv[i])+1] = '\0';
}
switch(choose)
{
case 1:
{
biaozhi = 1;
have(path);
break;
}
case 2:
{
biaozhi = 0;
have2(path);
break;
}
case 4:
{
R(head);
break;
}
case 3:
{
biaozhi = 1;
have2(path);
break;
}
case 6:
{
LR(head);
break;
}
case 7:
{
LR(head);
break;
}
case 5:
{
R(head);
break;
}
case 0:
{
biaozhi = 0;
have(path);
break;
}
default:break;
}
}
else
{
lstat(path,&buf);
switch(choose)
{
case 1:
{
printf("%s\n",path);
break;
}
case 2:
{
print_file(buf,path);
break;
}
case 4:
{
printf("%s\n",path);
break;
}
case 3:
{
print_file(buf,path);
break;
}
case 6:
{
print_file(buf,path);
break;
}
case 7:
{
print_file(buf,path);
break;
}
case 5:
{
printf("%s\n",path);
break;
}
default:break;
}
}
}
else
{
p = (struct rrr*)malloc(sizeof(struct rrr));
head = p;
p->next = NULL;
strcpy(p->a,"./");
strcpy(path,"./");
switch(choose)
{
case 1:
{
biaozhi = 0;
have(path);
break;
}
case 2:
{
biaozhi = 1;
have2(path);
break;
}
case 4:
{
R(head);
break;
}
case 6:
{
LR(head);
break;
}
case 3:
{
biaozhi = 1;
have2(path);
break;
}
case 7:
{
LR(head);
break;
}
case 5:
{
R(head);
break;
}
default:break;
}
}
return 0;
}