my_ls实现Linux shell命令ls

主要功能

  1. 实现-l , -a , -R ;
  2. 实现三种参数的组合调用;
  3. 在任何目录下都可以使用自己的ls命令;
  4. 自己的命令和系统的ls命令重定向输入到文件中去大小相差不能太多,1.25.倍以内;

实现过程

  • 一些重要的函数
    1. lstat()/stat()/fstat()函数解析;
    2. opendir()函数;
    3. 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中;
区别:

  1. 获取文件的状态信息的方式不同:statlstat是通过文件名获取文件信息的,而fstat是通过文件描述符获取文件信息的;
  2. 对于链接文件返回的状态信息不同:statfstat返回的是符号链接指向的文件状态信息,而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()函数传递参数时要传递文件的绝对路径,这里有两种方法来获取文件的绝对路径:

  1. 使用sprintf()函数对readdir()函数返回的结构体struct dirent中的文件名d_name与打开的目录进行字符串的拼接来获得文件的绝对路径
  2. 就是通过改变当前的工作工作目录,通过调用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代码分析

  1. 关于倒数第14行的closedir(dir)是因为在一个程序最多只能打开256个文件,所以我们必须要在遍历完这个目录后就关掉他.
  2. 关于链表,使用一个新的链表来存放当前目录里面的目录名,通过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;
}

最后建议大家写完自己的ls命令后实验以下命令:
./a.out -l /usr/bin
./a.out -Rl /
./a.out -Rl / > 1.txt 和 ls -Rl / > 2.txt 比较两个文件的大小

发布了35 篇原创文章 · 获赞 82 · 访问量 7551

猜你喜欢

转载自blog.csdn.net/qq_44049351/article/details/97189237
今日推荐