Linux C编程my_ls 的实现
功能
-
参数: -a -l -R -t -r -i -s
-
-R需要实现对根目录的查询
-
需要实现各种参数的随意组合
-
在所有目录下都可使用自己的my_ls
-
界面美观( 输出对齐等)
-
能屏蔽Ctrl+c杀死程序
-
按照文件类型以及权限的不同分颜色输出
流程图
完整代码
- 代码有一些问题,当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
他们的二进制如下表:
十进制 | 二 | 进 | 制 | |||||
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
8 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
16 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
32 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
64 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
若输入的参数里含有a,那么flag_param |= PARAM_A ,该操作将flag_param变为1,当还有参数R时,flag_param |= PARAM_RR,操作如下
flag_param=1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
---|---|---|---|---|---|---|---|---|
PARAM_RR=32 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
flag_param=33 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
我们用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_rR(int flag_param,char *fname); //递归
void display_R(int flag_param,char *fname); //递归
void display_dir(int flag_param,char *path); 由此函数进入上面两个函数
display函数
display函数是一个很重要的承前其后的函数,它承担这多个参数的组合实现,它来调用display_s 、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++;
printf("%d ", buf.st_ino);
len = strlen(name); //名字的长度
for(i=0;i<len;i++)
{
if(name[i] < 0)
{
j++;
}
}
len = g_maxlen - len + j/3;
sprintf(colorname,"\033[%dm%s\033[0m",filecolor,name);
printf(" %-s", colorname);
//输出若个个空格,补够一个单位
for(i=0;i<len+5;i++)
printf(" ");
if( h == han)
{
printf("\n");
h = 0;
}
}
-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,还要输出每一个文件名和大小
/******************************
*功能:输出文件信息 参数 -s
*参数1 文件名name
*参数2 输出文件颜色filecolor
******************************/
void display_s(char *name,int filecolor)
{
char colorname[NAME_MAX + 30];
int i,len,j = 0;
int a = 0,b = 0;
h++;
//名字的长度
len = strlen(name);
for(i=0;i<len;i++)
{
if(name[i] < 0)
{
j++;
}
}
len = g_maxlen - len + j/3;
struct stat buf;
if(stat(name,&buf) == -1)
{
perror("name");
exit(1);
}
sprintf(colorname,"\033[%dm%s\033[0m",filecolor,name);
printf("%2d",buf.st_blocks/2);
printf(" %-s", colorname);
//输出若个个空格,补够一个单位
for(i=0;i<len+5;i++)
printf(" ");
if(h == han)
{
printf("\n");
h = 0;
}
}
对齐问题
在对齐方面我们要计算出每一行输出多少个文件名,而且需要每个文件占相同的个数的位置,在这里我使用了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_s(char *name,int filecolor);
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中什么都不放是就不会杀死程序,