Linux C编程my_ls 的实现

Linux C编程my_ls 的实现(仅供参考)

功能

  • 参数: -a -l -R -t -r -i -s

  • -R需要实现对根目录的查询

  • 需要实现各种参数的随意组合

  • 在所有目录下都可使用自己的my_ls

  • 界面美观( 输出对齐等)

  • 能屏蔽Ctrl+c杀死程序

  • 按照文件类型以及权限的不同分颜色输出

流程图

在这里插入图片描述

完整代码

github

  • 代码有一些问题,当Rs组合时会出错,a与s和其他参数组合会没有.和 . .在这里插入图片描述

参数输入及处理

我们在终端使用ls时是以下格式:
ls
ls -a
ls -aR

我们自己写的my_ls.c可以用以下格式:

ycl@ycl-PC:~/Desktop/Linux文件$ gcc my_ls.c -o my_ls
ycl@ycl-PC:~/Desktop/Linux文件$ ./my_ls
ycl@ycl-PC:~/Desktop/Linux文件$ ./my_ls -a
ycl@ycl-PC:~/Desktop/Linux文件$ ./my_ls -aR
......
参数如何在./my_ls时传入函数

主函数main有两个参数,函数原型如下

int main(int argc, char * argv[]);

char *argv[]表示一个每一个元素都是一个char *型的指针,平时我们使用main函数时并为使用这两个参数,但argc在没有输入参数时为1,argv[0]指向的字符串为运行程序的文件名,例如:
test.c

#include <stdio.h>
int main(int argc, char *argv[]) 
{
    printf("my_ls.c的讲解");
 	return 0;
}

这个程序的argc为1,*argv[0]为test.c
当有参数输入时,就以my_ls.c为例子:

./my_ls -a时
	argc = 2
	*argv[0] = "my_ls.c"
	*argv[1] = "-a"
./my_ls -a -R时
	argc = 3
	*argv[0] = "my_ls.c"
	*argv[1] = "-a"
	*argv[2] = "-R"

这样就将参数储存到了argv和argc中

参数的处理

参数虽然传入到了函数中但以字符串的形式并不好使用,在my_ls.c中我们将字符串转化为数字,

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/limits.h>
#include <dirent.h>
#include <grp.h>
#include <pwd.h>
#include <errno.h>
#include <signal.h>

#define PARAM_NONE 0
#define PARAM_A    1              //参数a
#define PARAM_L    2              //参数l
#define PARAM_I    4              //参数i
#define PARAM_R    8              //参数r
#define PARAM_T    16             //参数t
#define PARAM_RR   32             //参数R
#define PARAM_S    64             //参数s
#define MAXROWLEN  155            //每行所用最大格数

int main(int argc, char *argv[])
{
    int i, j, k, num;
	char path[PATH_MAX+1];
	char param[32];
	int  flag_param = PARAM_NONE;
	struct stat  buf;

    j = 0;
    num = 0;

    signal(SIGINT, sighandler);

    //将参数提取到数组param中
    for(i = 1;i<argc;i++)
    {
        if(argv[i][0] == '-')
        {
            for(k = 1;k<strlen(argv[i]);j++,k++)
            {
                param[j] = argv[i][k];
            }
            num++;
        }
    }

    //将参数变形以数字形式保存进flag_param
    for(i = 0;i < j;i++)
    {
        if(param[i] == 'a')
        {
            flag_param |= PARAM_A;
        }
        else if(param[i] == 'l')
        {
            flag_param |= PARAM_L;
        }
        else if(param[i] == 'R')
        {
            flag_param |= PARAM_RR;
        }
        else if(param[i] == 'r')
        {
            flag_param |= PARAM_R;
        }
        else if(param[i] == 't')
        {
            flag_param |= PARAM_T;
        }
        else if(param[i] == 'i')
        {
            flag_param |= PARAM_I;
        }
        else if(param[i] == 's')
        {
            flag_param |= PARAM_S;
        }
        else
        {
            printf("ls:不适用的选项 -- %c", param[i]);
            exit(1);
        }
    }


    //判断是否有目录输入,没有则打开根目录./
    if(num + 1 == argc)
    {
		strcpy(path,"./");
		path[2] = '\0';
		display_dir(flag_param,path);
		return 0;
    }

	i = 1;
	do
	{
        if(argv[i][0] == '-')
        {
			i++;
			continue;
		} 
        else 
        {
            //得到具体路径(目录名)
			strcpy(path, argv[i]);
			if(stat(path,&buf) == -1)
				my_err("stat",__LINE__);
 
	    	//判断是否为目录文件	
            if(S_ISDIR(buf.st_mode))
			{
                //如果目录最后忘记了/则加上
				if(path[strlen(argv[i]) - 1] != '/')
				{
					path[strlen(argv[i])]  = '/';
					path[strlen(argv[i])+1] = '\0';
				}
				else 
					path[strlen(argv[i])] = '\0';
			
                display_dir(flag_param,path);//按照目录输出
				i++;
			}
            else
			{
                //按照文件输出
				display(flag_param,path);
				i++;
			}
		}
	}while (i < argc);
    return 0;
}

我们先将除*argv[1]外的参数储存到char *param中(‘-’不需要存入),但这也不方便使用,这里我们使用位运算 |= 和 &。在宏定义时我们定义了

#define PARAM_NONE 0                  //无参数
#define PARAM_A    1                  //参数a
#define PARAM_L    2                  //参数l
#define PARAM_I    4                  //参数i
#define PARAM_R    8                  //参数r
#define PARAM_T    16                 //参数t
#define PARAM_RR   32                 //参数R
#define PARAM_S    64                 //参数s

他们的二进制如下表:

十进制
000000000
100000001
200000010
400000100
800001000
1600010000
3200100000
6401000000

若输入的参数里含有a,那么flag_param |= PARAM_A ,该操作将flag_param变为1,当还有参数R时,flag_param |= PARAM_RR,操作如下

flag_param=100000001
PARAM_RR=3200100000
flag_param=3300100001

我们用flag_param将参数储存为数字形式,使用时依然是用位操作,

if(flag_param & PARAM_RR)
{
	printf("含有R参数");
}

flag_param & PARAM_RR运算的结果为32不等于0,条件真,这样就用简单的一个int型的数代替了复杂的指针数组。

目录参数的处理

在流程图中我们看见,需要判断输入参数中是否有目录名,如果有就时要输出目录的ls -参数,没有则输出从根目录开始的ls -参数;输入目录和参数是有不同的,参数前面有‘-’,但目录没有,我们可以根据这个来判断是否有目录输入,如果有则将目录strcpy给字符数字path,如果没有则吧"./"(根目录)赋给path。

参数的实现

参数的意思

要实现这些参数就先了解这些参数所表示的意思

参数功能
-a显示所有档案及目录(包括隐藏目录和文件)
-l显示详细文件内容
-s在每个文件名后输出该文件的大小
-r对目录反向排序
-i输出文件的i节点的索引信息
-t以文件修改时间排序
-R递归打开所有目录
文件名的获取

要想要获取文件名就要用到opendir和readdir两个函数,它们可以打开目录。使用完后需要closedir关闭。
例如:

/***************************
 * 
 * 列出文件名
 * 
 * ************************/

#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int my_readir(const char *path)
{
    DIR *dir;
    struct dirent *ptr;

    if((dir = opendir(path)) == NULL)
    {
        perror("opendir");
        return -1;
    }
    while((ptr = readdir(dir)) != NULL)
    {
        printf("file name:%s\n", ptr->d_name);
    }
    closedir(dir);

    return 0;
}

int main(int argc, char **argv)
{
    if(argc < 2)
    {
        printf("listfile <target path>\n");
        exit(1);
    }

    if(my_readir(argv[1]) < 0)
    {
        exit(1);
    }

    return 0;
}

opendir用来打开参数path目录,并返回DIR*形态的目录流,readdir用来从参数dir所指向的目录中读取目录项信息,返回一个struct dirent结构的指针。

struct dirent{
	long d_ino;
	off_t d_off;
	unsigned short d_reclen;
	char d_name[NAME_MAX+1];
}
  • d_ino是此目录i节点编号,不必理会;
  • d_off是指目录文件开头至此目录进入点的位移;
  • d_reclen是指长度;
  • d_name是指以NULL结尾的文件名;

readdir函数执行成功返回该目录下一个文件的信息(储存在dirent结构体中),如果调用opendir打开某目录后,第一次调用该函数,则返回的是该目录下第一关文件的信息,第二次调用该函数返回该目录下第二个文件的信息,依此类推。
closedir用来关闭dir指向的目录。

stat函数

在这里我们再介绍一个函数stat函数,它的函数原型为:

int stat(const char *path, struct stat *buf)

path为文件名,struct stat * buf是一个保存文件状态信息的结构体,其类型如下:

struct stat{
	dev_t       st_dev;       //文件的设备编号
	ino_t       st_ino;       //文件的i-node(i节点编号)
	mode_t      st_mode;      //文件的类型和存取权限,它的含义与chmod,open函数的mode参数相同
	nlink_t     st_nlink;     //连到该文件的硬件链接数目,刚建立的文件值为1.
	uid_t       st_uid;       //文件所有者的用户ID
	gid_t       st_gid;       //文件所有组的组ID
	dev_t       st_rdev;      //若此文件为设备文件,则为其设备编号
	off_t       st_size;      //文件大小,以字节计算,对符号链接,该大小是其所指向文件名的长度
	blksize_t   st_blksize;   //文件系统的I/O缓存区大小
	blkcnt_t    st_blocks;    //占用文件区块的个数,每一区块大小通常为512个字节
	time_t      st_atime;     //文件最后一次被访问的时间
	time_t      st_mtime;     //文件最后一次被修改的时间,一般只能调用utime和write函数时才会变化
	time_t      st_ctime;     //文件最近一次被修改的时间,此参数在文件所有者、所属组、文件权限被更改时更新
}

对于st_mode包含的文件类型信息,POSIX标准定义了一系列的宏。

  • S_ISLNK(st_mode): 判断是否为符号链接
  • S_ISREG(st_mode):判断是否为一般文件
  • S_ISDIR(st_mode):判断是否为目录文件
  • S_ISCHR(st_mode):判断是否为字符设备文件
  • S_ISBLK(st_mode):判断是否为快设备文件
  • S_ISFIFO(st_mode):判断是否为先进先出FIFO
  • S_ISSOCK(st_mode):判断是否为socket

struct stat结构体的成员比较多,但有的很少使用,常用的有:st_mode、st_uid、st_gid、st_size、st_atime、st_mtime。
在./下创建文件test.c,然后用下面程序查看该文件的权限:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>

int main()
{
    struct stat buf;
	char *name;
    if(stat("./test.c", &buf) == -1)
    {
        perror("test.c");
        exit(1);
    }
	//scanf("%s",&name);
	//lstat(name, &buf);
    /****************文件类型**********************/
	if(S_ISLNK(buf.st_mode)){
		printf("l");
	} else if(S_ISREG(buf.st_mode)){
		printf("-");
	} else if(S_ISDIR(buf.st_mode)){
		printf("d");
	} else if(S_ISCHR(buf.st_mode)){
		printf("c");
	} else if(S_ISBLK(buf.st_mode)){
		printf("b");
	} else if(S_ISFIFO(buf.st_mode)){
		printf("f");
	} else if(S_ISSOCK(buf.st_mode)){
		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("-");


	return 0;
}
参数的实现-r -R -t

这三个参数中-r 和 -t都是排序,所以我们可以在获取文件名时就先将他们排好序:

	//插入排序
	if(flag_param & PARAM_T)        //根据时间排序
	{
        flag_param -= PARAM_T;
		for(i = 0;i<count;i++)
		{
			stat(filename[i],&buf);       //用buf获取文件filename[i]中的数据
			filetime[i][0] = buf.st_mtime;
		}

		for(i = 0;i<count;i++)
		{
			for(j = i;j<count;j++)
			{
				if(filetime[i][0]<filetime[j][0])
				{
					/*交换时间filetime还要叫唤文件名*/
					t = filetime[i][0];
					filetime[i][0] = filetime[j][0];
					filetime[j][0] = t;
					strcpy(temp,filename[i]);
					strcpy(filename[i],filename[j]);
					strcpy(filename[j],temp);
				}
			}
		}
	}
	else//根据名字排序
	{
		for(i=0;i<count;i++)
		{
			for(j=i;j<count;j++)
			{
				if(strcmp(filename[i],filename[j])>0)
				{
					strcpy(temp,filename[i]);
					strcpy(filename[i],filename[j]);
					strcpy(filename[j],temp);
				}
			}
		}
	}

-R参数的实现需要用到递归,这是ls当中的一个难点,在终端使用ls -R时你会发现先输出
./: 然后换行输出./下的文件名包括目录名,再换行输出./下的目录例如./mulu/: 在换行输出该目录下的文件名和目录名,依次类推;下面是my_ls.c中实现-R -r -t的部分代码:

代码很长这里就不给出在github上完整的代码里实现这部分的函数是
void display_dir(int flag_param,char *path);
display函数

display函数是一个很重要的承前其后的函数,它承担这多个参数的组合实现,它来调dispaly_attribute 、display_st_ino 、display_single函数来实现复杂的组合。

参数-l -i 的实现

-l表示要输出详细的文件的信息:

/*********************************************
 * 功能 :输出文件信息  参数  -l
 * 参数1  文件信息stat结构体
 * 参数2  文件名name
 * 参数3  文件显示颜色filecolor
 *********************************************/
void display_attribute(struct stat buf,char * name,int filecolor)
{
    char colorname[NAME_MAX + 30];
	char buf_time[32];
	struct passwd *psd;
	struct group  *grp;
/****************文件类型**********************/
	if(S_ISLNK(buf.st_mode)){
		printf("l");
	} else if(S_ISREG(buf.st_mode)){
		printf("-");
	} else if(S_ISDIR(buf.st_mode)){
		printf("d");
	} else if(S_ISCHR(buf.st_mode)){
		printf("c");
	} else if(S_ISBLK(buf.st_mode)){
		printf("b");
	} else if(S_ISFIFO(buf.st_mode)){
		printf("f");
	} else if(S_ISSOCK(buf.st_mode)){
		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("\t");	
    //通过用户和组id得到用户的信息和其所在组的信息
	psd = getpwuid(buf.st_uid);
	grp = getgrgid(buf.st_gid);
 
	printf("%4d ",buf.st_nlink);    //打印文件的硬链接数
	printf("%-8s",psd->pw_name);    //打印用户的名字
	printf("%-8s", grp->gr_name);   //打印用户组的名字
 
	printf("%6d", buf.st_size);     //打印文件大小
	strcpy(buf_time,ctime(&buf.st_mtime));//把时间转换成普通表示格式
 
	buf_time[strlen(buf_time)-1]='\0';    //去掉换行符
	printf("  %s", buf_time);//输出时间 
    sprintf(colorname,"\033[%dm%s\033[0m",filecolor,name);
	printf(" %-s\n", colorname);
}

-i参数需要输出文件的i节点的索引信息:

/********************************************
 * 功能:输出带有i_node的文件信息
 * 参数1 文件信息
 * 参数2 文件名
 * 参数3 显示颜色
 * *****************************************/
void display_st_ino(struct stat buf,char *name,int filecolor)
{
	char colorname[NAME_MAX + 30];
	int i,len,j = 0;
	h++;
	len = strlen(name);
	for(i=0;i<len;i++)
	{
		if(name[i] < 0)
		{
			j++;
		}
	}
	len = len - j/3;
	//printf("%d",len);
	if(len < 40)
	{
		len = 40 - len;
	}
	else
	{
		printf("\n");
	}
	printf("%d ", buf.st_ino);
	sprintf(colorname,"\033[%dm%s\033[0m",filecolor,name);
	printf(" %-s", colorname);
	//输出少于要求,补够空格
    for(i=0;i<len+5;i++)
		printf(" ");
	g_leave_len = g_leave_len - 45;
	if(g_leave_len < 45)
	{
		printf("\n");
		g_leave_len=MAXROWLEN;
	}
}

-a 和-s的实现(-a 和-s的实现分散在程序中)

我们先看-a的实现,该参数的实现其实很简单,当我们在display中就解决了这个问题:

if(flag_param & PARAM_A)
{
	这个时候我们就直接输出文件名
}
else
{
	这个时候我们判断是否文件名为.. .如果是就不输出不是就输出
}

-s的需要输出文件大小,而且在开头需要输出所有文件大小的总量,在函数display_dir和 display_rR 和display_R中都有下面的片段,用来计算总量total

      //计算总用量total
    if(flag_param & PARAM_A)
    {
        for(i = 0;i<count;i++)
        {
            stat(filename[i],&name);
            total = total + name.st_blocks/2;
        }
    }
    else
    {
        for(i = 0;i<count;i++)
        {
            stat(filename[i],&name);
            if(filename[i][2] != '.')
            {
                total = total + name.st_blocks/2;
            }
        }
    }

这部分只是为开头输出总用量计算出total,还要输出每一个文件名和大小
我们用下面代码输出

printf("%2d  ",buf.st_blocks/2);

对齐问题

在对齐方面我们要计算出每一行输出多少个文件名,而且需要每个文件占相同的个数的位置,在这里我使用了g_maxlen来表示最长文件名,用g_leave_len来表示一行的最大位子数。han表示一行输出的文件名的个数,经过试验发现以下方式最适合,你可以找到你电脑上适合的计算方法

han = g_leave/(g_maxlen+15);

在对齐方面有一个难点是汉字文件名和英文文件名的混合,在代码中你可能会奇怪我计算文件名大小的方法,平时使用的strlen函数计算汉字字符串时会出现问题例如:

#include <stdio.h>
#include <string.h>

int main()
{
	char *name = "文件名大小size.c";
	//方法一,strlen函数
	int len = strlen(name);
	printf("strlen(name) = %d\n", len);


	//方法二:
	int a = 0;               //用来统计汉字的个数,个数 = a/3
	int b = 0;               //用来统计非汉字的个数 b
	for(int i = 0; i < strlen(name); i++)
	{
		if(name[i]< 0)
		{
			a++;
		}
		else
		{
			b++;
		}	
	}
	len = a/3 + b;
	printf("size(name) = %d",len);

	return 0;
}

输出结果:
strlen(name) = 21              //方法一
size(name) = 11                //方法二

而且汉字和普通字符输出时会有不同,汉字所占大小是普通字符的二倍,这个问题在以下几个函数中都需要解决,你可以仔细观察看看。

void display_single(char *name,int filecolor);
void display_st_ino(struct stat buf,char *name,int filecolor);

输出文件颜色

对于不同类型的文件我们输出不同颜色的文件名,格式:

printf("\033[字背景颜色;字体颜色m字符串\033[0m" );

部分颜色代码:

字背景颜色: 40–49字颜色: 30–39
40: 黑30: 黑
41: 红31: 红
42: 绿32: 绿
43: 黄33: 黄
44: 蓝34: 蓝
45: 紫35: 紫
46: 深绿36: 深绿
47:白色37:白色

防止ctrl + c 杀死程序

程序在运行是可以使用ctrl + c来杀死程序,例如:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void sighandler(int);

int main()
{
   signal(SIGINT, sighandler);

   while(1) 
   {
      printf("开始休眠一秒钟...\n");
      sleep(1);
   }

   return(0);
}

void sighandler(int signum)
{
   printf("捕获信号 %d,跳出...\n", signum);
   exit(1);
}

运行结果
开始休眠一秒钟...
开始休眠一秒钟...
开始休眠一秒钟...
开始休眠一秒钟...      //输入ctrl + c
^C捕获信号 2,跳出...

结束程序的是sighandler函数中的exit(0),当函数sighandler中什么都不放是就不会杀死程序,

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值