文章目录

  • 三、编写ls命令
    • 1. 阅读联机帮助
    • 2. ls是如何工作的
    • 3. 如何编写ls
    • 4. 改进ls命令
    • 5. ls -l 是如何工作的
    • 6. 如何编写 ls -l

三、编写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命令

加入以下功能:

  1. 排序
    解决办法:把所有的文件名读入一个数组,用qsort函数排序

  2. 分栏:标准的 ls 输出是分栏排列的,有些以行排列,有些以列排列
    解决办法:把文件名读入数组,然后计算出列的宽度和行数

  3. “.”文件:ls 列出了 “.”文件,而标准的 ls只有在给出 -a 选项时才会列出
    解决办法:使 ls1 能够接收选项 -a,并在没有 -a 的时候不显示隐藏文件

  4. 选项 -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命令相关推荐

  1. linux没有jre文件夹,linux上配置jdk时,java命令提示没有此文件或文件夹的解决方法...

    linux上配置jdk时,java命令提示没有此文件或文件夹的解决方法 出现这个问题可能有以下几种原因: 1.对该文件没有执行的权限. 2.我们的机器是64位的,而下载的jdk是32位的. 我就是后一 ...

  2. Java:Linux上java -jar xxx.jar命令执行jar包时出现Error: Invalid or corrupt jarfile xxx.jar解决方案

    Java:Linux上java -jar xxx.jar命令执行jar包时出现Error: Invalid or corrupt jarfile xxx.jar解决方案 参考文章: (1)Java:L ...

  3. linux sftp怎样支持通配符,linux上的sftp与scp命令

    linux下的sftp与scp命令 第一个(sftp安全文件传输)是一个类ftp的客户端程序,它能够被用来在网络中传输文件.它并不使用FTP守护进程(ftpd或wu-ftpd)来进行连接,而是有意义地 ...

  4. Linux文件与目录操作 ls 命令(2)

    说文件操作是最频繁地操作也不为过,在Linux中,使用ls命令可以列出当前目录中所有内容,本篇就先说说ls命令.本文所说的文件指文件和目录. ls命令常用选项 -a:显示指定目录下所有子目录与文件,包 ...

  5. linux服务器 图片压缩,Pngquant:Linux上压缩PNG图像的命令行实用工具

    [51CTO.com快译]Pngquant是一款免费开源的跨平台命令行有损PNG压缩工具.它基于可移植的libimagequant库,用C99编写.可通过将PNG图像转换成更高效的8位PNG格式来显著 ...

  6. linux写一个ls命令,linux 下 如何自己写 ls 命令

    有过linux 基础 都知道 ls 命令的作用 下面给出实现代码 #include #include #include #include #include #include #include #inc ...

  7. Linux使用C语言实现ls命令

    原理 在linux下使用C语言,通过调用Linux系统的目录访问API来实现一个类似于ls命令功能的小程序,主要是可以练习程序对命令的解析和目录API函数的使用. 实现代码 #include < ...

  8. linux中ls-f的用法,ls命令--Linux命令应用大词典729个命令解读

    内容来源于人民邮电出版社<Linux命令应用大词典> 讲述729个命令,1935个例子 学习Linux系统的参考书.案头书,遇到不懂的命令或命令选项一查即可 争取每天都发布内容 本文出自 ...

  9. 使用ls命令查看Linux的目录结构,linux查看工作目录文件ls命令用法详解

    查看工作目录文件ls ls命令是Linux下最常用的命令.ls命令就是list的缩写.默认情况下ls用来查看当前目录的清单,如果ls指定其他目录,那么就会显示指定目录里的文件及文件夹清单.通过ls命令 ...

最新文章

  1. 极端值目标值(exterem or outlier target)对应的核心特征的分布差异分析+结合catboost特种重要度(top10)
  2. Spring Cloud Stream 学习小清单
  3. ibatis提示Unable to load embedded resource from assembly Entity.Ce_SQL.xml,Entity.
  4. oracle ogg常用指令,oracle goldengate日常管理命令
  5. asp.net core监控—引入Prometheus(一)
  6. JavaWeb项目实战(1)数据库环境搭载
  7. java 堆排序算法_堆排序算法的讲解及Java版实现
  8. mysql ssh 导入时注意问题
  9. 北京二环内详细矢量地图(MapInfo格式)
  10. 【Java后台】从零开始的Java后台开发(一)
  11. GIS相关网站、社区、论坛收藏
  12. win10浏览器闪退_Win10专业版下Edge浏览器闪退的多种解决技巧
  13. 2022为什么一定要学网络安全?
  14. 双河市谷歌高清卫星地图下载
  15. 您不知道Android的ES File Explorer可以做的19件事
  16. 5G标准核心内容:R15+R16(内含赠书福利)
  17. python编写古诗_用Python实现古诗词填字游戏(一)
  18. 网络协议 -- ICMP协议(1) 报文格式
  19. totolinkn200up怎么设置_TOTOLINK EX300无线中继器设置说明
  20. IBM V7000存储Mdisk磁盘掉线数据恢复_服务器数据恢复

热门文章

  1. [创业-17]:财务报表之综述
  2. 浅谈应变式扭矩传感器原理
  3. 56个民族select下拉框
  4. 计算机审计未来,关于计算机审计的几点看法
  5. GIT 技巧命令讲解笔记2020 (二)
  6. HTML+CSS完成的动画页面
  7. 微信:微信扫码支付、调用统一下单接口、网站支付 + springmvc
  8. 计算机体系结构 标量处理机
  9. fatal error: sdf sdf.hh: No such file or directory include sdf sdf.hh 解决办法
  10. 《爱因斯坦死亡方程式》——一部关于爱因斯坦传奇一生的纪录片