用linux系统调用实现类似终端的ll命令
一、思路分析
在终端输入
ll
会在终端打印该目标文件夹下的所有文件
获得的信息如下:
- 每个文件的信息独占一行
- 文件的类型、权限、所属用户,创建时间等都是需要用到stat()函数的
- 目录的颜色、可执行文件的颜色及其后字符均与其他文件不一样
- 链接文件是有箭头指向源文件的
了解 linux 的目录接口函数
在此,会用到三个结构体(或者说是三个主要的函数):
typedef struct __dirstream DIR;
struct dirent
struct stat
对应的三个函数原型是:
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int stat(const char *pathname, struct stat *statbuf);
想要获取某目录下(比如include目录下)stdio.h文件的详细信息,我们应该怎样做?
- 首先,我们使用opendir函数打开目录include,返回指向目录include的DIR结构体pf。
- 接着,我们调用readdir(pf)函数读取目录include下所有文件(包括目录),返回指向目录include下所有文件的dirent结构体d。
- 然后,我们遍历d,调用stat(d->name,sta)来获取每个文件的详细信息,存储在stat结构体sta中。
总体就是这样一种逐步细化的过程,在这一过程中,三种结构体扮演着不同的角色。
//DIR 结构体
struct __dirstream
{
void *__fd;
char *__data;
int __entry_data;
char *__ptr;
int __entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (, __lock)
};
struct dirent //目录内容
{
long d_ino; /* inode number 索引节点号 */
off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
unsigned short d_reclen; /* length of this d_name 文件名长 */
unsigned char d_type; /* the type of d_name 文件类型 */
char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
}
struct stat //文件的详细信息
{
dev_t st_dev; /* ID of device containing file -文件所在设备的ID*/
ino_t st_ino; /* inode number -inode节点号*/
mode_t st_mode; /* protection -文件访问权限*/
nlink_t st_nlink; /* number of hard links -链向此文件的连接数(硬连接)*/
uid_t st_uid; /* user ID of owner -user id*/
gid_t st_gid; /* group ID of owner - group id*/
dev_t st_rdev; /* device ID (if special file) -设备号,针对设备文件*/
off_t st_size; /* total size, in bytes -文件大小,字节为单位*/
blksize_t st_blksize; /* blocksize for filesystem I/O -系统块的大小*/
blkcnt_t st_blocks; /* number of blocks allocated -文件所占块数*/
time_t st_atime; /* time of last access -上次存取时间*/
time_t st_mtime; /* time of last modification -上次修改时间*/
time_t st_ctime; /* time of last status change - 创建时间*/
};
二、代码实现
2.1 打开目录流
在此为了方便,利用main函数的命令行参数去打开对应路径
int main(int argc, char *argv[])
{
@msg
argc : 是命令行参数的个数
*argv[] : 是命令行参数的地址
}
// 而在此只需要一个参数,因此取argv[1],即可。
打开目录流
int main(int argc, char *argv[])
{
printf("open %s\n",argv[1]);
DIR *pdir = opendir(argv[1]);
if(!pdir)
{
perror("open dir");
return -1;
}
}
2.2 获取目录下的文件
//先定义一个dirent结构体指针去接收readdir的返回值,并将目录流指针传入readdir函数中。
struct dirent *file;
file = readdir(pdir)
{
@return
dirent *:失败返回NULL
}
因为要读取目录下的所有文件,因此要在一个循环中完成,一般是如下:
struct dirent *file;
while(file = readdir(pdir))
{
//要干的事情。
}
2.3 获取每个文件的详细信息
同理也是要先定义一个stat结构体变量,使其获取文件属性信息。
struct stat _sta, *sta = &_sta;
int stat(const char *pathname, struct stat *buf);
{
@msg
*pathname:路径及其文件名
*buf:获取到的文件属性信息就记录在 struct stat 结构体中 。
@return
int :失败返回-1.并设置errorno。
}
现在的问题就是如和获取到该目录下的所有文件的路径及其文件名。
路径已经知道,就是main的第一个命令行参数argv[1],文件名就是dirent结构体下的d_name成员。
因此获得完整的路径和文件名就很简单了:使用sprintf或strcat函数皆可。
在这里简单使用一下fprintf函数:
struct dirent *file;
char path[300]; //因为在dirent结构体中d_name成员的长度有255.
struct stat _sta, *sta = &_sta; //定义stat结构体
while(file = readdir(pdir))
{
sprintf(path,"%s/%s",argv[1],(char *)file->d_name); //将路径和文件名用/拼接起来
//此时的path就是完整的路径和文件名了。
ret_stat = stat(path, sta);
if(ret_stat == -1)
{
perror("stat");
return -1;
}
//在此每个文件的详细信息就获取到了stat结构体定义的_sta变量中了。
}
2.4 获取文件的类型和读改写权限
在stat结构体的st_mode下有给我们的信息很多,因此在获取权限和文件类型时,需要屏蔽其他干扰位,最简单的办法就是使用按位与,其功能是与0为0,与1不变。
//获取文件的类型
char *pl[]={"s","l","-","b","d","c","p","?"};
char *type(int a) //获得类型的函数
{
switch(a)
{
case 014:return pl[0];
case 012:return pl[1];
case 010:return pl[2];
case 006:return pl[3];
case 004:return pl[4];
case 002:return pl[5];
case 001:return pl[6];
default:
return pl[7];
}
}
mode_t var_type = (sta->st_mode & 0170000) >> 12;
//获取文件的权限
char *p[]={"---","--x","-w-","r--","-wx","rw-","r-x","rwx","err"};
char *chomd(int a)//获得权限的函数
{
switch(a)
{
case 0:return p[0];
case 1:return p[1];
case 2:return p[2];
case 3:return p[4];
case 4:return p[3];
case 5:return p[6];
case 6:return p[5];
case 7:return p[7];
default:
return p[8];
}
}
chomd((sta->st_mode & 0070)>>3); //调用时通过右移来确定哪位用户的权限。
2.5 获取文件的所属用户和用户组
在stat结构体定义了st_uid和st_gid,这两个是用户的id,而不是用户的名称。需要使用**getpwuid()和getgrgid()**函数通过id获取到名称
getpwuid(sta->st_uid)->pw_name; //所属用户
getgrgid(sta->st_gid)->gr_name; //用户组
2.6 获取文件的大小
stat结构体下的st_size表示的就文件的大小
2.7 转换文件的创建时间
st_mtime的时间是自1900.1.1至今的秒数,在c库中有一个函数即可将时间转换为日常生活中的风格。
#include <time.h>
struct tm *localtime(const time_t * calptr);
// 将时间数值变换成本地时间,考虑到本地时区和夏令时标志;
struct tm {
int tm_sec; /* 秒 – 取值区间为[0,59] */
int tm_min; /* 分 - 取值区间为[0,59] */
int tm_hour; /* 时 - 取值区间为[0,23] */
int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
int tm_year; /* 年份,其值等于实际年份减去1900 */
int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一 */
int tm_yday; /* 从每年1月1日开始的天数– 取值区间[0,365],其中0代表1月1日 */
int tm_isdst; /* 夏令时标识符,夏令时tm_isdst为正;不实行夏令时tm_isdst为0 */
};
2.8 文件名的不同颜色
printf()函数在Linux下的功能极其强大,它可以用以些特殊符号让字符在终端显示不同的颜色,包括粗体、斜体、背景色、前景色等。
int printf ( const char * format, ... );
//其一般用法为
printf("hello ");
//只需要在双引号内加上如下转义
"\e[?;?;?m " //问号表示数字,以 \e[ 开头,以m结尾,参数用分号分隔,可缺省为一个参数
printf("\e[33mhello ");
字背景颜色范围:40----49
40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色
字颜色:30-----------39
30:黑
31:红
32:绿
33:黄
34:蓝色
35:紫色
36:深绿
37:白色
0:默认
三、完整代码
/**************************************************************
* File Name : ll.c
* Creat Time : 2022年11月02日 星期三 18时33分42秒
* 备注 :
***************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
char *p[]={"---","--x","-w-","r--","-wx","rw-","r-x","rwx","err"};
char *pl[]={"s","l","-","b","d","c","p","?"};
char *type(int a)
{
switch(a)
{
case 014:return pl[0];
case 012:return pl[1];
case 010:return pl[2];
case 006:return pl[3];
case 004:return pl[4];
case 002:return pl[5];
case 001:return pl[6];
default:
return pl[7];
}
}
char *chomd(int a)
{
switch(a)
{
case 0:return p[0];
case 1:return p[1];
case 2:return p[2];
case 3:return p[4];
case 4:return p[3];
case 5:return p[6];
case 6:return p[5];
case 7:return p[7];
default:
return p[8];
}
}
int print(char *str, int mode, char *typ)
{
if(*typ == '-')
switch(mode)
{
case 3:;
case 2:;
case 1:return printf("\e[1;32m%s\e[0m*\n",str);
case 0:return printf("%s\n",str);
default:return -1;
}
else if(*typ =='d')
return printf("\e[1;34m%s\e[0m/\n",str);
return 0;
}
int main(int argc, char *argv[])
{
printf("open %s\n",argv[1]);
DIR *pdir = opendir(argv[1]);
if(!pdir)
{
perror("open dir");
return -1;
}
struct dirent *file;
struct stat _sta, *sta = &_sta;
int ret_stat = 1, mode_tmp = 0, count = 0;
time_t now;
struct tm *date;
char path[300], linkbuf[300];
while(file = readdir(pdir))
{
sprintf(path,"%s/%s",argv[1],(char *)file->d_name);
ret_stat = stat(path, sta);
if(ret_stat == -1)
{
return -1;
}
mode_t tmp = sta->st_mode & 0777;
mode_t var_type = (sta->st_mode & 0170000) >> 12;
date = localtime(&sta->st_mtime);
printf("%s%s%s%s",
type(var_type),
chomd((sta->st_mode & 0700)>>6),
chomd((sta->st_mode & 0070)>>3),
chomd(sta->st_mode & 0007));
printf("%3ld%6s%6s%8ld%3d月%3d %02d:%02d ",
sta->st_nlink,
getpwuid(sta->st_uid)->pw_name,
getgrgid(sta->st_gid)->gr_name,
sta->st_size,
date->tm_mon+1,
date->tm_mday,
date->tm_hour,
date->tm_min
);
mode_tmp = (tmp & 1) + ((tmp & 0100)>>6) + ((tmp & 0010) >> 3);
print(file->d_name, mode_tmp, type(var_type));
count++;
//printf("\n\n%ld\n",file->d_off);
fflush(stdout);
usleep(100000);
}
printf("\n共%d个文件\n",count);
closedir(pdir);
return 0;
}
四、效果
五、总结
总的来说,该例是要一个要熟练理解有关目录和文件的三个结构体的用途,并通过man手册查阅对应函数作用和用法,就能得到较高的效率。