在Linux上实现自定义的 ls命令
Posted ClimberCoding
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Linux上实现自定义的 ls命令相关的知识,希望对你有一定的参考价值。
三、编写ls命令
1. 阅读联机帮助
列出有关文件的信息(默认为当前目录)。如果未指定 -cftuvSUX 或 --sort,则按字母顺序对条目进行排序。
可以看到,ls命令 能够找出当前目录中所有文件的文件名,按字典序排序后输出。
ls命令 还能显示其他信息,如果加上 -l 选项,ls 会列出每个文件的详细信息,也叫 ls的长格式,在 man手册 中可以看到:
使用长列表格式
现在在我们的终端键入命令:
通过实验和联机帮助可以知道 ls 做了以下两件事(ls 能判定参数指定的是文件还是目录):
- 列出目录的内容
- 显示文件的信息
在正式开始之前,来看一下 Unix 是如何组织磁盘上的文件的。
大方框表示目录,大方框内的小方框表示文件,目录之间的连线表示目录之间的组织关系。
2. ls是如何工作的
通过联机帮助(过程省略)可以知道,从目录读数据与从文件读数据是类似的, opendir 打开一个目录,readdir 返回目录中的当前项,closedir 关闭一个目录,seekdir、telldir、rewinddir与 lseek 的功能类似。
接下来用 man手册 查询一下 readdir(3) ,可以看到:
readdir() 函数返回一个指向 dirent 结构的指针,该结构表示 dirp 指向的目录流中的下一个目录条目。它在到达目录流末尾或发生错误时返回 NULL。
也就是说, readdir() 来读取 struct dirent
获得目录中的记录。
3. 如何编写ls
最初级的ls命令
下面实现了一个最初级的 ls命令
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h> // opendir() readdir() closedir()
void do_ls(char*);
int main(int argc, char* argv[]) {
if (argc == 1) {
do_ls(".");
}
else {
while (--argc) {
printf("%s:\\n", *(++argv));
do_ls(*argv);
}
}
return 0;
}
void do_ls(char dirname[]) {
DIR* dir_ptr; // 记录opendir()后的返回值
struct dirent* direntp; // 记录readdir()后的返回值
if ((dir_ptr = opendir(dirname)) == NULL) {
fprintf(stderr, "ls1: cannot open %s\\n", dirname);
}
else {
while ((direntp = readdir(dir_ptr)) != NULL) {
printf("%s\\n", direntp->d_name);
}
closedir(dir_ptr);
}
}
运行结果:
4. 改进ls命令
加入以下功能:
-
排序
解决办法:把所有的文件名读入一个数组,用qsort函数排序 -
分栏:标准的 ls 输出是分栏排列的,有些以行排列,有些以列排列
解决办法:把文件名读入数组,然后计算出列的宽度和行数 -
“.”
文件:ls 列出了“.”
文件,而标准的 ls只有在给出 -a 选项时才会列出
解决办法:使 ls1 能够接收选项 -a,并在没有 -a 的时候不显示隐藏文件 -
选项 -l:如果选项中有 -l,标准的 ls会列出文件的详细信息,而 ls1不会
解决办法: 下面讨论
5. ls -l 是如何工作的
下面我们将把 ls -l 拆分成几个小组件逐一分析并实现:
1. 用 stat 得到文件信息
根据 man手册 所提供的信息,再去查询 fstatat,可以看到如下的信息:
下面写一个程序将以上我们需要的属性显示出来:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
void show_stat_info(char* fname, struct stat* buf);
int main(int argc, char* argv[]) {
struct stat info;
if (argc > 1) {
if (stat(argv[1], &info) != -1) {
show_stat_info(argv[1], &info);
return 0;
}
else {
perror(argv[1]);
}
}
return 0;
}
void show_stat_info(char* fname, struct stat* buf) {
printf(" mode: %o\\n", buf->st_mode);
printf(" links: %d\\n", buf->st_nlink);
printf(" user: %d\\n", buf->st_uid);
printf(" group: %d\\n", buf->st_gid);
printf(" size: %d\\n", buf->st_size);
printf("modtime: %d\\n", buf->st_mtim.tv_sec);
printf(" name: %s\\n", fname);
}
运行结果:
对比可以看到,链接数、文件大小 的显示都没问题,最后修改时间是 time_t 类型,用 ctime 将其转换为字符串也可以解决。
为了进一步完善 ls -l ,我们需要进一步处理 模式、用户名和组 的显示
2. 将模式字段转换成字符
st_mode 是一个16位的二进制数,文件类型和权限被编码在这个数中,如图所示:
- 其中前 4 位用作文件类型,最多可以标识 16 种类型,1 代表具有某个属性,0 代表没有,目前已经使用了其中的 7 个
- 接下来的 3 位是文件的特殊属性,1 代表具有某个属性,0 表示没有,这 3 位分别是 set-user-ID位、set-group-ID位 和 sticky位
- 最后的 9 位是许可权限,分为 3 组,对应 3 种用户,它们是文件所有者、同组用户和其他用户。
每组 3 位,分别是读、写和执行的权限(相应的地方如果是 1,就说明该用户拥有对应的权限,0 代表没有)
如何读取被编码的值?
利用 子域编码 与 掩码 的技术。
对 2 进制进行位与操作,即我们所说的解码。判断目录代码:
if((info.st_mode & 0170000) == 0040000) {
printf("this is a directory");
}
通过掩码把其他无关的部分置为 0,再与表示目录的代码比较,从而判断这是否是一个目录。
更简单的方法是用 #include <sys/stat.h>
中的宏代替上述代码:
#define S_ISFIFO(m) (((m)&(0170000)) == (0010000))
#define S_ISDIR(m) (((m)&(0170000)) == (0040000))
#define S_ISCHR(m) (((m)&(0170000)) == (0020000))
#define S_ISBLK(m) (((m)&(0170000)) == (0060000))
#define S_ISREG(m) (((m)&(0170000)) == (0100000))
使用宏后就这样写代码:
if(S_ISDIR(info.st_mode)) {
printf("this is a directory");
}
下面实现将模式字段转换为字符
#include <sys/stat.h>
void mode_to_letters(int mode, char str[]) {
strcpy(str,"----------");
if(S_ISDIR(mode)) str[0]='d';
if(S_ISCHR(mode)) str[0]='c';
if(S_ISBLK(mode)) str[0]='b';
if(mode & S_IRUSR) str[1]='r';
if(mode & S_IWUSR) str[2]='w';
if(mode & S_IXUSR) str[3]='x';
if(mode & S_IRGRP) str[4]='r';
if(mode & S_IWGRP) str[5]='w';
if(mode & S_IXGRP) str[6]='x';
if(mode & S_IROTH) str[7]='r';
if(mode & S_IWOTH) str[8]='w';
if(mode & S_IXOTH) str[9]='X';
}
现在还剩下最后一个要解决的问题,文件所有者(user) 和 组(group) 的表示
3. 将用户/组 ID转换成字符串
用户
这里需要用到库函数 getpwuid() 来访问用户列表,getpwuid 需要 UID(user ID)作为参数,返回一个指向 struct passwd 的指针,这个结构定义在 /usr/include/pwd.h
中,通过 man手册查询 getpwuid 可以看到:
继续往下翻,
结构体 struct passwd 正是 ls -l 所需要的信息,实现代码:
#include <pwd.h>
char* uid_to_name(uid_t uid) {
return getpwuid(uid)->pw_name;
}
这段代码很简单,但不够健壮,如果 uid 不是一个合法的用户 ID,那 getpwuid 返回空指针 NULL,这时 getpwuid(uid)->pw_name 失去了意义。
常用的 ls命令 有一种处理这种情况的办法。(这里不做讨论)
组
文件 /etc/group
是一个保存所有的组信息的文本文件。在网络计算系统中,组信息也被保存在 NIS 中。(另外,前面讨论的 用户的信息 保存在 NIS 中)
Unix 系统提供 getgrgid()
函数来访问组列表,通过 man手册查询 getgrgid 可以看到:
继续往下翻,
实现代码:
#include <grp.h>
char* gid_to_name(gid_t gid) {
return getgrgid(gid)->gr_name;
}
6. 如何编写 ls -l
通过上面的分析,下面实现最终的代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h> // mode_to_letters() show_stat_info()
#include <dirent.h> // opendir() readdir() closedir()
#include <pwd.h> // getpwuid()
#include <grp.h> // getgrgid()
#include <string.h> // strcpy()
#include <time.h> // ctime()
void do_ls(char dirname[]);
void dostat(char* filename);
void show_file_info(char* fname, struct stat* buf);
// void mode_to_letters(int mode, char str[]);
char* uid_to_name(uid_t uid);
char* gid_to_name(gid_t gid);
int main(int argc, char* argv[]) {
if (argc == 1) {
do_ls(".");
}
else {
while (--argc) {
printf("%s:\\n", *(++argv));
do_ls(*argv);
}
}
return 0;
}
void do_ls(char dirname[]) {
DIR* dir_ptr; // 记录opendir()后的返回值
struct dirent* direntp; // 记录readdir()后的返回值
if ((dir_ptr = opendir(dirname)) == NULL) {
fprintf(stderr, "ls1: cannot open %s\\n", dirname);
}
else {
while ((direntp = readdir(dir_ptr)) != NULL) {
// printf("%s\\n", direntp->d_name);
dostat(direntp->d_name);
}
closedir(dir_ptr);
}
}
void dostat(char* filename) {
struct stat info; // 存储filename的信息
if (stat(filename, &info) == -1) {
perror(filename);
}
else {
show_file_info(filename, &info);
}
}
void show_file_info(char* filename, struct stat* info_p) {
// char* uid_t_name(), *ctime(), *gid_to_name();
void mode_to_letters();
char modestr[11];
mode_to_letters(info_p->st_mode, modestr); // 将模式字段转换成字符
printf("%s", modestr);
printf("%4d", (int)info_p->st_nlink);
printf(" %-10s", uid_to_name(info_p->st_uid)); // 将用户ID转换成字符串
printf("%-10s", gid_to_name(info_p->st_gid)); // 将组ID转换成字符串
printf("%8ld", (long)info_p->st_size);
printf("%.12s", 4 + ctime(&info_p->st_mtim.tv_sec)); // 通过ctime()函数转换时间,之前的who命令有用到
printf(" %s\\n", filename);
}
// 将模式字段转换成字符
void mode_to_letters(int mode, char str[]) {
strcpy(str,"----------");
// 用到了 子域编码 与 掩码 的技术
if(S_ISDIR(mode)) str[0]='d';
if(S_ISCHR(mode)) str[0]='c';
if(S_ISBLK(mode)) str[0]='b';
if(mode & S_IRUSR) str[1]='r';
if(mode & S_IWUSR) str[2]='w';
if(mode & S_IXUSR) str[3]='x';
if(mode & S_IRGRP) str[4]='r';
if(mode & S_IWGRP) str[5]='w';
if(mode & S_IXGRP) str[6]='x';
if(mode & S_IROTH) str[7]='r';
if(mode & S_IWOTH) str[8]='w';
if(mode & S_IXOTH) str[9]='X';
}
// 将用户ID转换成字符串
char* uid_to_name(uid_t uid) {
// struct passwd* getpwuid(), *pw_ptr;
struct passwd* pw_ptr;
static char numstr[10];
if ((pw_ptr = getpwuid(uid)) == NULL) {
sprintf(numstr, "%d", uid);
return numstr;
}
else {
return pw_ptr->pw_name;
}
}
// 将组ID转换成字符串
char* gid_to_name(gid_t gid) {
// struct group* getgrgid(), *grp_str;
struct group* grp_str;
static char numstr[10];
if ((grp_str = getgrgid(gid)) == NULL) {
sprintf(numstr, "%d", gid);
return numstr;
}
else {
return grp_str->gr_name;
}
}
运行结果:
我们自己编写的 ls2 对比 系统提供的 ls -l 效果已经很不错了,模式字段、用户名和组名的处理均已完成。剩下的 隐藏 “.”
,显示记录总数等功能暂时不再讨论。
以上是关于在Linux上实现自定义的 ls命令的主要内容,如果未能解决你的问题,请参考以下文章
使用 Angular 4 在 datatables.net 中实现自定义搜索
-bash: /usr/bin/ls: /lib64/ld-linux-x86-64.so.2: bad ELF interpreter: No such file or directory(代码片段
Android 逆向Linux 文件权限 ( Linux 权限简介 | 系统权限 | 用户权限 | 匿名用户权限 | 读 | 写 | 执行 | 更改组 | 更改用户 | 粘滞 )(代码片段