前情提要

在笔者的上一篇博客Linux系统编程【3.1】——编写ls命令中,实现了初级版的ls命令,但是与原版ls命令相比,还存在着显示格式和无颜色标记的不同。经过笔者近两天的学习,基本解决了这两个问题,且实现了"ls -l",并对于可选参数"-a"和"-l"有了更好的支持(不管-a,-l输入顺序如何,是"ls -a -l",还是"ls -l -a",还是"ls -al",亦或是"ls -ls",出现位置几何,重复与否,都能正确运行)。

ls显示格式的解决

首先,让我们来观察一下原版ls显示的格式:



笔者总结出的原版ls显示规律:

  • 1.按序显示
  • 2.按照排序规则按列从上往下显示,当前列显示完成后转到下一列继续显示
  • 3.每一列都是左对齐的
  • 4.每一列的宽度都是该列最长文件名长度加2
  • 5.列的数目要尽量保证每一行都被文件名“填充满”,而又不会导致行中最后一个文件名换行

在我们自己实现ls显示时,如何满足这5个规律呢?

对于规律1,笔者已经采用排序算法来满足了。规律3可以采用printf函数中的"%-*s"来满足。其他三条规律好像不太好满足。问题的关键在于,我们不知道要显示的行数和列数,以及每一列的最大字符串长度。为了获得这些数据,接下来介绍笔者琢磨出来的一种算法,姑且将其称为“分栏算法”吧。

分栏算法

问题可以简化为:给你一个已经排序的字符串指针数组,求能满足上述5个规律输出显示这些字符串的行/列数,以及每一列的最大字符串长度。

那么就让我们来构建这样一个函数签名:

由于要返回三个不同数据,所以将其中一个作为返回值,另外两个作为指针传入参数。

1.确定函数返回值:返回行数

2.确定函数参数:字符串指针数组、字符串个数、列数指针、每列的最大字符串长度数组

函数签名如下所示:

  1. int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr);

"囫囵吞枣"版分栏算法

在函数主体中,先计算出

  1. 最少占用字符数 =(每个字符串长度+2)* 总字符串个数

加2是因为在显示时每个字符串后面至少跟两个空格。利用ioctl函数调用,获取当前屏幕宽度(一行能容纳的字符数)。用最少占用字符数除以屏幕宽度,得到一个基准行数。这个基准行数是最少所需行数,因为只有在每个字符串长度都一致,且加2之后的长度能整除屏幕宽度时,才只需要这么少的行数就能装得下。

当字符串之间长度差距较大时,所需的空间就越多。比如同一列中,一个字符串老长了,其他的比较短,为了满足规律4,那么短的字符串后面跟的空格数就大大增加。

获得基准行数之后,预分配基准行数*屏幕宽度的字符数大小,就将字符串一一从上到下(排基准行数行),从左到右排列(列数保存给列数指针),同时对于每一列都获取最大字符串长度(存到每列最大字符串长度数组中),重新计算所占总字符数时,将每一列最大字符串长度乘以行数的值算进去。然后将所占总字符数减去预分配的大小,继续分配足够的行来容纳这个差值。

迭代进行上述步骤,直到所占总字符数不大于预分配的大小。返回最后分配的行数。

如下所示为图解:



之所以说它是“囫囵吞枣”,是因为在处理超额长度时,直接取最长的那一个代表最后一列的所有字符串长度,一刀切了。这可能导致在某些情况下格式显示不正确。

源代码如下:

  1. int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr)
  2. {
  3. //获取路径名中的文件名之前的长度
  4. int path_len = strlen(filenames[0]);
  5. while(filenames[0][path_len] != '/'){
  6. --path_len;
  7. }
  8. ++path_len;
  9. struct winsize size;
  10. ioctl(STDIN_FILENO,TIOCGWINSZ,&size);
  11. int col = size.ws_col; //获得当前窗口的宽度(字符数)
  12. int col_max = 0;
  13. int cur_file_size = 0;
  14. int filenames_len[file_cnt];
  15. *cal_col = 0;
  16. int i = 0;
  17. //获得每个文件名的字符长度
  18. for(i = 0;i < file_cnt;++i){
  19. filenames_len[i] = strlen(filenames[i]) - path_len;
  20. cur_file_size += (filenames_len[i] + 2); //计算所有文件名字符数加两个空格的总字符数
  21. }
  22. //最小行数,在此基础上迭代
  23. int base_row = cur_file_size / col;
  24. if(cur_file_size % col){
  25. base_row++;
  26. }
  27. int pre_allc_size = 0;
  28. do{
  29. *cal_col = 0;
  30. pre_allc_size = base_row * col;
  31. cur_file_size = 0;
  32. for(i = 0;i < file_cnt;++i){
  33. //当前列的最后一行
  34. if(i % base_row == base_row - 1){
  35. ++(*cal_col);
  36. col_max = (filenames_len[i] > col_max) ? filenames_len[i] : col_max;
  37. cur_file_size += (col_max + 2) * base_row;
  38. col_max_arr[*cal_col-1] = col_max;
  39. col_max = 0;
  40. }
  41. //非最后一行
  42. else{
  43. col_max = (filenames_len[i] > col_max) ? filenames_len[i] : col_max;
  44. }
  45. }
  46. //最后一列未满
  47. if(i % base_row){
  48. ++(*cal_col);
  49. cur_file_size += (col_max + 2) * (i % base_row);
  50. col_max_arr[*cal_col-1] = col_max;
  51. col_max = 0;
  52. }
  53. int dis = 0;
  54. if(cur_file_size > pre_allc_size){
  55. dis = cur_file_size - pre_allc_size;
  56. }
  57. base_row += (dis / col);
  58. if(dis % col){
  59. ++base_row;
  60. }
  61. }while(cur_file_size > pre_allc_size);
  62. return base_row;
  63. }

在进行输出时,从左往右,从上到下打印,后续在代码中体现。

"精打细算"版分栏算法

在计算基准行数时,与"囫囵吞枣"的分栏算法一样。区别就在于对于超额列的处理上。设定一个当前屏幕剩余宽度变量,对于每一个字符串,当其长度超过剩余宽度,则终止排列,所需的额外空间大小为剩余未排列的所有字符串块长度之和。

下图所示为图解:



正因为计算超额空间时,算的是每个未排列字符串块长度的真实长度,所以是“精打细算”。并且每次都检测是否超剩余宽度,所以能严格保证最终显示格式的正确性。

源代码如下:

  1. int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr)
  2. {
  3. //获取路径名中的文件名之前的长度
  4. int path_len = strlen(filenames[0]);
  5. while(filenames[0][path_len] != '/'){
  6. --path_len;
  7. }
  8. ++path_len;
  9. struct winsize size;
  10. ioctl(STDIN_FILENO,TIOCGWINSZ,&size);
  11. int col = size.ws_col; //获得当前窗口的宽度(字符数)
  12. int col_max = 0;
  13. int cur_file_size = 0;
  14. int filenames_len[file_cnt];
  15. *cal_col = 0;
  16. int i = 0;
  17. int j = 0;
  18. //获得每个文件名的字符长度
  19. for(i = 0;i < file_cnt;++i){
  20. filenames_len[i] = strlen(filenames[i]) - path_len + 2; //字符串至少带两个空格
  21. /*特殊情况:当最大字符串长度比屏幕宽度大时,直接返回行数:file_cnt,列数:1,最大宽度:最大的字符串长度*/
  22. if(filenames_len[i] > col){
  23. *cal_col = 1;
  24. col_max_arr[0] = filenames_len[i];
  25. return file_cnt;
  26. }
  27. cur_file_size += filenames_len[i];
  28. }
  29. //最小行数,在此基础上迭代
  30. int base_row = cur_file_size / col;
  31. if(cur_file_size % col){
  32. base_row++;
  33. }
  34. int flag_succeed = 0; //标记是否排列完成
  35. //开始排列
  36. while(!flag_succeed){
  37. int remind_width = col; //当前可用宽度
  38. *cal_col = -1;
  39. for(i = 0;i < file_cnt;++i){
  40. /*如果剩余宽度不足以容纳当前字符串,则跳出并分配额外的行空间*/
  41. if(filenames_len[i] > remind_width){
  42. break;
  43. }
  44. //新起的一列
  45. if(i % base_row == 0){
  46. ++(*cal_col);
  47. col_max_arr[*cal_col] = filenames_len[i];
  48. }
  49. else{
  50. col_max_arr[*cal_col] = (filenames_len[i] > col_max_arr[*cal_col]) ? filenames_len[i] : col_max_arr[*cal_col];
  51. }
  52. //最后一行,更新剩余的宽度
  53. if(i % base_row == base_row - 1){
  54. remind_width -= col_max_arr[*cal_col];
  55. }
  56. }
  57. //判断是否排列完成
  58. if(i == file_cnt){
  59. flag_succeed = 1;
  60. }
  61. //再分配额外行空间
  62. else{
  63. int extra = 0; //所需额外的字符数
  64. while(i < file_cnt){
  65. extra += filenames_len[i++];
  66. }
  67. if(extra % col){
  68. base_row += (extra / col + 1);
  69. }
  70. else{
  71. base_row += (extra / col);
  72. }
  73. }
  74. }
  75. ++(*cal_col); //列标从0开始,所以最后加1
  76. return base_row;
  77. }

分栏效果展示与不足

两种分栏算法的比对

1."囫囵吞枣"版分栏算法和原版ls的对比



前一个是原版ls命令显示效果,后一个是笔者实现的ls03("囫囵吞枣"版)命令显示效果,基本一致。

2."精打细算"版分栏算法和"囫囵吞枣"版分栏算法比对

之前提到过在某些情况下,"囫囵吞枣"版分栏算法格式显示不正确。下图就是一个例子:



可以看到,"囫囵吞枣(ls03)"显示格式不对,但"精打细算(ls04)"就没问题。所以"精打细算"版分栏算法是"囫囵吞枣"版分栏算法的优化。

不足之处

目前来看,即使是更厉害的"精打细算"版分栏算法,也存在两个主要的不足点。

其一是排序与原版不同,笔者利用的是ASCII值来进行字符串排序的,对于大小写字母,特殊符号和汉字,没有做到像原版ls那样合适。

其二是在显示汉字文件名时,格式不正确,如下所示:



显示出来的存在没有左对齐的列。笔者推测,汉字的字符串单位长度和显示出的所占宽度不是一比一的关系,导致计算时存在偏差。进一步推测,除了汉字,对于其他的特殊符号,只要单位长度和显示出的所占宽度不是一比一,都会存在偏差。如果想要解决这个问题,就要查找这个对应关系,然后进行特定的转换。

ls显示颜色的解决

因为要查找有关颜色方面的信息,笔者通过linux指导信息,确定dircolors这个命令可以给我提供有用的信息,输入:

  1. man dircolors

显示信息如下:



其中有一个选项是:dircolors -p,能够打印出所有默认的颜色信息。我们输入:

  1. dircolors -p > color

">"表示将dircolors -p命令的输出结果保存到color文件中(若没有则自动创建)。

然后输入:

  1. vim color

就可以进入文件查看其中内容了(当然用"more color"命令也行,只是笔者习惯用"vim"命令),如下所示:



对于每种类型的文件,都给出了默认的颜色值。

其中关于c语言颜色打印,为了防止本文篇幅过长,影响阅读体验,可自行查阅资料,这里随便给出一个链接供参考:

printf打印颜色

接下来的事情就是如何获得指定的文件类型,到这里就引出了下一节内容:ls -l的实现。

ls -l的实现

获取stat结构体内容

首先看一下原版ls -l的输出内容:

  • 第一部分:模式(mode),每行的第一个字符表示文件类型。"-"表示普通文件,"d"表示目录等等。接下来的9个字符表示文件访问权限,分为读权限、写权限和执行权限,又分别针对3种对象:用户、同组用户和其他用户。
  • 第二部分:链接数列(links),表示该文件被引用次数。
  • 第三部分:文件所有者(owner),表示文件所有者的用户名
  • 第四部分:组(group),表示文件所有者所在的组名
  • 第五部分:大小(size),表示文件所占字节数
  • 第六部分:最后修改时间(laste-modified)
  • 第七部分:文件名(name)

如何获得这些信息?笔者通过查阅书籍,知道了一个"stat"函数,能帮助我们拿到上述信息。

输入:

  1. man 2 stat

ps:直接输入man stat得到的是stat(1),是一个终端命令,不是我们想要的,在其中的“SEE ALSO”找到的stat(2)才是我们需要的。



将文件路径传入stat函数,可以获得一个stat类型的结构体,该结构体定义如下:



太棒了,我们要的信息它都有!

格式转换

但是不要高兴的太早,如果直接打印这个stat结构体里面的st_mode、st_uid、st_gid和st_mtime,显示的是一些数字,所以还要对它们一一转换成字符串再输出。

st_mode位运算

st_mode实际上是一个16位的二进制数,其结构如下:



Linux中文件被分为7大类,也就是有7种不同的编码,我们只要提取其中的type位,然后与定义的文件类比对就能知道该文件类型了。同样的,对于权限位,只要此位为1,就置为'r'、'w'或'x',否则置为'-'。

如何判断特定的位的值呢?这里就需要掩码这个东西了,笔者在计算机网络中学过类似的玩意--ip地址掩码。掩码的作用就是将其他位遮挡住,只暴露所需的位。即掩码将其他的位设为0,所需的位设为1,利用"与"运算(&),把待处理的数和掩码相与,将结果与预先存放的数对比看是否一致。

  1. //定义判断文件类型的宏
  2. #define S_ISFIFO(m) (((m)&(0170000))==(0010000))
  3. #define S_ISDIR(m) (((m)&(0170000))==(0040000))
  4. #define S_ISCHR(m) (((m)&(0170000))==(0020000))
  5. #define S_ISBLK(m) (((m)&(0170000))==(0060000))
  6. #define S_ISREG(m) (((m)&(0170000))==(0100000))
  7. #define S_ISLNK(m) (((m)&(0170000))==(0120000))
  8. #define S_ISSOCK(m) (((m)&(0170000))==(0140000))
  9. #define S_ISEXEC(m) (((m)&(0000111))!=(0))

举例来说:上图中第一个掩码是0170000,第一个0表示这个数是八进制的,转换为二进制为1111000000000000,即type位全部为1,假设st_mode为0001000000000000,相与之后得到0001000000000000,即八进制的0100000,查表就知道这是一个regular文件。

同理,上图中第二个掩码是0000111,它把'x'位设为1,其他位为0,与st_mode相与后,如果st_mode中任意一个'x'位为1,那么结果就不等于0,由此可以判断文件是否为可执行文件。

这样,我们就可以利用掩码和预定义的值来协助完成模式字符串的转换。

st_uid/st_gid/st_mtime格式转换

借助getpwuid函数,getgrgid函数和ctime函数分别完成用户名、组名和时间格式的转换。

ls -l效果展示

源码

ls可选参数:-a ,-l

  1. /*
  2. * ls04.c
  3. * writed by lularible
  4. * 2021/02/09
  5. * ls -a -l
  6. */
  7. #include<stdio.h>
  8. #include<stdlib.h>
  9. #include<malloc.h>
  10. #include<sys/types.h>
  11. #include<dirent.h>
  12. #include<string.h>
  13. #include<sys/ioctl.h>
  14. #include<unistd.h>
  15. #include<termios.h>
  16. #include<sys/stat.h>
  17. #include<time.h>
  18. #include</home/lularible/bin/sort.h>
  19. #include<pwd.h>
  20. #include<grp.h>
  21. /*
  22. //定义判断文件类型的宏,系统中已经定义过了,这里写出来供参考
  23. #define S_ISFIFO(m) (((m)&(0170000))==(0010000))
  24. #define S_ISDIR(m) (((m)&(0170000))==(0040000))
  25. #define S_ISCHR(m) (((m)&(0170000))==(0020000))
  26. #define S_ISBLK(m) (((m)&(0170000))==(0060000))
  27. #define S_ISREG(m) (((m)&(0170000))==(0100000))
  28. #define S_ISLNK(m) (((m)&(0170000))==(0120000))
  29. #define S_ISSOCK(m) (((m)&(0170000))==(0140000))
  30. */
  31. #define S_ISEXEC(m) (((m)&(0000111))!=(0))
  32. //函数声明
  33. void do_ls(const char*); //主框架
  34. void error_handle(const char*); //错误处理
  35. void restored_ls(struct dirent*,const char*); //暂存文件名
  36. void color_print(char*,int,int); //根据文件类型打印不同颜色的文件名
  37. void showtime(long);
  38. void show_with_l(); //显示带-l参数的内容
  39. void show_without_l(); //显示不带-l参数的内容
  40. int cal_row(char**,int,int*,int*); //为分栏算法提供要显示的行/列数
  41. void mode_to_letters(int,char*); //-l中,转换mode属性为字符串
  42. char* uid_to_name(uid_t); //-l中,转换uid为字符串
  43. char* gid_to_name(gid_t); //-l中,转换gid为字符串
  44. //全局变量
  45. int a_flag = 0; //是否带-a参数
  46. int l_flag = 0; //是否带-l参数
  47. char* dirnames[4096]; //暂存目录名
  48. int dir_cnt = 0; //目录个数
  49. char* filenames[4096]; //暂存目录中文件名
  50. int file_cnt = 0;
  51. int main(int argc,char* argv[])
  52. {
  53. //存储文件名和所带参数
  54. while(--argc){
  55. ++argv;
  56. //遇到可选参数
  57. if(*argv[0] == '-'){
  58. while(*(++(*argv))){
  59. if(**argv == 'a'){
  60. a_flag = 1;
  61. }
  62. else if(**argv == 'l'){
  63. l_flag = 1;
  64. }
  65. else{
  66. error_handle(*argv);
  67. }
  68. }
  69. }
  70. //遇到目录名
  71. else{
  72. dirnames[dir_cnt++] = *argv;
  73. }
  74. }
  75. if(dir_cnt == 0){
  76. dirnames[dir_cnt++] = ".";
  77. }
  78. sort(dirnames,0,dir_cnt - 1);
  79. //对filenames中文件名,对于不同的可选参数的处理
  80. int i = 0;
  81. DIR* cur_dir;
  82. struct diren* cur_item;
  83. //遍历目录文件
  84. for(i = 0;i < dir_cnt;++i){
  85. printf("以下是”%s“的内容:\n",dirnames[i]);
  86. do_ls(dirnames[i]);
  87. }
  88. }
  89. void do_ls(const char* dir_name)
  90. {
  91. DIR* cur_dir;
  92. struct dirent* cur_item;
  93. file_cnt = 0;
  94. //打开目录
  95. if((cur_dir = opendir(dir_name)) == NULL){
  96. error_handle(dir_name);
  97. }
  98. else{
  99. //读取目录并显示信息
  100. //将文件名存入数组
  101. while((cur_item = readdir(cur_dir))){
  102. restored_ls(cur_item,dir_name);
  103. }
  104. //字典序排序
  105. sort(filenames,0,file_cnt-1);
  106. //输出结果
  107. if(l_flag){
  108. //带-l参数
  109. show_with_l();
  110. }
  111. else{
  112. //不带-l参数
  113. show_without_l();
  114. }
  115. //释放内存
  116. int i = 0;
  117. for(i = 0;i < file_cnt;++i){
  118. free(filenames[i]);
  119. }
  120. //关闭目录
  121. closedir(cur_dir);
  122. }
  123. }
  124. void restored_ls(struct dirent* cur_item,const char* dir_name)
  125. {
  126. char* file_path = (char*)malloc(sizeof(char)*256);
  127. strcpy(file_path,dir_name);
  128. char* result = cur_item->d_name;
  129. strcat(file_path,"/");
  130. strcat(file_path,result);
  131. //当不带-a参数时,隐藏以‘.'开头的文件
  132. if(!a_flag && *result == '.') return;
  133. filenames[file_cnt++] = file_path;
  134. }
  135. void show_without_l()
  136. {
  137. int i = 0;
  138. int j = 0;
  139. struct stat file_info; //保存文件属性的结构体
  140. //分栏算法
  141. int col = 0;
  142. int col_max_arr[256];
  143. int row = cal_row(filenames,file_cnt,&col,col_max_arr);
  144. for(i = 0;i < row;++i){
  145. for(j = 0;j < col;++j){
  146. if((j*row+i) < file_cnt){
  147. if(stat(filenames[j*row+i],&file_info) != -1){
  148. int mode = file_info.st_mode;
  149. color_print(filenames[j*row+i],mode,col_max_arr[j]);
  150. }
  151. else{
  152. error_handle(filenames[j*row+i]);
  153. }
  154. }
  155. }
  156. printf("\n");
  157. }
  158. }
  159. void show_with_l()
  160. {
  161. struct stat info;
  162. char modestr[11];
  163. int i = 0;
  164. for(i = 0;i < file_cnt;++i){
  165. if(stat(filenames[i],&info) == -1){
  166. error_handle(filenames[i]);
  167. }
  168. else{
  169. mode_to_letters(info.st_mode,modestr);
  170. printf("%s",modestr);
  171. printf("%4d ",(int)info.st_nlink);
  172. printf("%-12s",uid_to_name(info.st_uid));
  173. printf("%-12s",gid_to_name(info.st_gid));
  174. printf("%-8ld ",(long)info.st_size);
  175. showtime((long)info.st_mtime);
  176. color_print(filenames[i],info.st_mode,0);
  177. printf("\n");
  178. }
  179. }
  180. }
  181. void showtime(long timeval)
  182. {
  183. char* tm;
  184. tm = ctime(&timeval);
  185. printf("%24.24s ",tm);
  186. }
  187. void color_print(char* pathname,int filemode,int width)
  188. {
  189. //截取路径名中的文件名
  190. char* filename;
  191. int len = strlen(pathname);
  192. while(pathname[len] != '/'){
  193. --len;
  194. }
  195. filename = &pathname[len+1];
  196. if(S_ISDIR(filemode)){
  197. printf("\033[01;34m%-*s\033[0m",width,filename);
  198. }
  199. else if(S_ISLNK(filemode)){
  200. printf("\033[01;36m%-*s\033[0m",width,filename);
  201. }
  202. else if(S_ISFIFO(filemode)){
  203. printf("\033[40;33m%-*s\033[0m",width,filename);
  204. }
  205. else if(S_ISSOCK(filemode)){
  206. printf("\033[01;35m%-*s\033[0m",width,filename);
  207. }
  208. else if(S_ISBLK(filemode)){
  209. printf("\033[40;33;01m%-*s\033[0m",width,filename);
  210. }
  211. else if(S_ISCHR(filemode)){
  212. printf("\033[40;33;01m%-*s\033[0m",width,filename);
  213. }
  214. else if(S_ISREG(filemode)){
  215. if(S_ISEXEC(filemode)){
  216. printf("\033[01;32m%-*s\033[0m",width,filename);
  217. }
  218. else{
  219. printf("%-*s",width,filename);
  220. }
  221. }
  222. else{
  223. printf("%-*s",width,filename);
  224. }
  225. }
  226. int cal_row(char** filenames,int file_cnt,int* cal_col,int* col_max_arr)
  227. {
  228. //获取路径名中的文件名之前的长度
  229. int path_len = strlen(filenames[0]);
  230. while(filenames[0][path_len] != '/'){
  231. --path_len;
  232. }
  233. ++path_len;
  234. struct winsize size;
  235. ioctl(STDIN_FILENO,TIOCGWINSZ,&size);
  236. int col = size.ws_col; //获得当前窗口的宽度(字符数)
  237. int col_max = 0;
  238. int cur_file_size = 0;
  239. int filenames_len[file_cnt];
  240. *cal_col = 0;
  241. int i = 0;
  242. int j = 0;
  243. //获得每个文件名的字符长度
  244. for(i = 0;i < file_cnt;++i){
  245. filenames_len[i] = strlen(filenames[i]) - path_len + 2; //字符串至少带两个空格
  246. //特殊情况:当最大字符串长度比屏幕宽度大时,直接返回行数:file_cnt,列数:1,最大宽度:最大的字符串长度
  247. if(filenames_len[i] > col){
  248. *cal_col = 1;
  249. col_max_arr[0] = filenames_len[i];
  250. return file_cnt;
  251. }
  252. cur_file_size += filenames_len[i];
  253. }
  254. //最小行数,在此基础上迭代
  255. int base_row = cur_file_size / col;
  256. if(cur_file_size % col){
  257. base_row++;
  258. }
  259. int flag_succeed = 0; //标记是否排列完成
  260. //开始排列
  261. while(!flag_succeed){
  262. int remind_width = col; //当前可用宽度
  263. *cal_col = -1;
  264. for(i = 0;i < file_cnt;++i){
  265. //如果剩余宽度不足以容纳当前字符串,则跳出并分配额外的行空间
  266. if(filenames_len[i] > remind_width){
  267. break;
  268. }
  269. //新起的一列
  270. if(i % base_row == 0){
  271. ++(*cal_col);
  272. col_max_arr[*cal_col] = filenames_len[i];
  273. }
  274. else{
  275. col_max_arr[*cal_col] = (filenames_len[i] > col_max_arr[*cal_col]) ? filenames_len[i] : col_max_arr[*cal_col];
  276. }
  277. //最后一行,更新剩余的宽度
  278. if(i % base_row == base_row - 1){
  279. remind_width -= col_max_arr[*cal_col];
  280. }
  281. }
  282. //判断是否排列完成
  283. if(i == file_cnt){
  284. flag_succeed = 1;
  285. }
  286. //再分配额外行空间
  287. else{
  288. int extra = 0; //所需额外的字符数
  289. while(i < file_cnt){
  290. extra += filenames_len[i++];
  291. }
  292. if(extra % col){
  293. base_row += (extra / col + 1);
  294. }
  295. else{
  296. base_row += (extra / col);
  297. }
  298. }
  299. }
  300. ++(*cal_col); //列标从0开始,所以最后要加1
  301. return base_row;
  302. }
  303. void mode_to_letters(int mode,char* str)
  304. {
  305. strcpy(str,"----------"); //文件类型与权限,先占好10个坑位
  306. //这里只转换7种文件类型常见的三种
  307. if(S_ISDIR(mode)) str[0] = 'd';
  308. if(S_ISCHR(mode)) str[0] = 'c';
  309. if(S_ISBLK(mode)) str[0] = 'b';
  310. //权限转换
  311. //当前用户权限
  312. if(mode & S_IRUSR) str[1] = 'r';
  313. if(mode & S_IWUSR) str[2] = 'w';
  314. if(mode & S_IXUSR) str[3] = 'x';
  315. //当前组其他用户权限
  316. if(mode & S_IRGRP) str[4] = 'r';
  317. if(mode & S_IWGRP) str[5] = 'w';
  318. if(mode & S_IXGRP) str[6] = 'x';
  319. //其他
  320. if(mode & S_IROTH) str[7] = 'r';
  321. if(mode & S_IWOTH) str[8] = 'w';
  322. if(mode & S_IXOTH) str[9] = 'x';
  323. }
  324. char* uid_to_name(uid_t uid)
  325. {
  326. struct passwd* pw_ptr;
  327. static char numstr[10];
  328. if((pw_ptr = getpwuid(uid)) == NULL){
  329. sprintf(numstr,"%d",uid);
  330. return numstr;
  331. }
  332. else{
  333. return pw_ptr->pw_name;
  334. }
  335. }
  336. char* gid_to_name(gid_t gid)
  337. {
  338. struct group* grp_ptr;
  339. static char numstr[10];
  340. if((grp_ptr = getgrgid(gid)) == NULL){
  341. sprintf(numstr,"%d",gid);
  342. return numstr;
  343. }
  344. else{
  345. return grp_ptr->gr_name;
  346. }
  347. }
  348. void error_handle(const char* name)
  349. {
  350. perror(name);
  351. }

总结

本次优化了ls显示效果,实现了ls -l。ls -l就是业务多了一点,需要进行多个格式转换处理,算法上无太多磕绊的点,比较通顺。笔者的大部分时间都花费在实现ls的"分栏算法"上,其中的算法逻辑值得研究,而且肯定还有更好的优化算法。看起来很简单的一个ls显示,蕴藏着的技术细节却不少,这就是linux的魅力所在吧。

参考资料

《Understanding Unix/Linux Programming A Guide to Theory and Practice》

欢迎大家转载本人的博客(需注明出处),本人另外还有一个个人博客网站:lularible的个人博客,欢迎前去浏览。

Linux系统编程【3.2】——ls命令优化版和ls -l实现的更多相关文章

  1. Linux系统编程【3.1】——编写ls命令

    ls命令简介 老规矩,直接在终端输入:man ls (有关于man命令的简介可以参考笔者前期博客:Linux系统编程[1]--编写more命令) 可以看到,ls命令的作用是显示目录中的文件名,它带有可 ...

  2. Linux系统编程【2】——编写who命令

    学到的知识点 通过实现who命令,学到了: 1.使用man命令寻找相关信息 2.基于文件编程 3.体会到c库函数与系统调用的不同 4.加深对缓冲技术的理解 who命令的作用 who命令的使用 在控制终 ...

  3. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  4. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  5. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  6. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  7. Linux C 程序 文件操作(Linux系统编程)(14)

    文件操作(Linux系统编程) 创建一个目录时,系统会自动创建两个目录.和.. C语言实现权限控制函数 #include<stdio.h> #include<stdlib.h> ...

  8. linux系统编程:cp的另外一种实现方式

    之前,这篇文章:linux系统编程:自己动手写一个cp命令 已经实现过一个版本. 这里再来一个版本,涉及知识点: linux系统编程:open常用参数详解 Linux系统编程:简单文件IO操作 /*= ...

  9. 《Linux系统编程(第2版)》

    <Linux系统编程(第2版)> 基本信息 作者: (美)Robert Love 译者: 祝洪凯 李妹芳 付途 出版社:人民邮电出版社 ISBN:9787115346353 上架时间:20 ...

随机推荐

  1. 入门OJ:Coin

    题目描述 你有n个硬币,第i硬币面值为ai,现在总队长想知道如果丢掉了某个硬币,剩下的硬币能组成多少种价值?(0价值不算) 输入格式 第一行一个整数n 第二行n个整数.,a1,a2-an. 1< ...

  2. Vue使用Ref跨层级获取组件实例

    目录 Vue使用Ref跨层级获取组件实例 示例介绍 文档目录结构 安装vue-ref 根组件自定义方法[使用provide和inject] 分别说明各个页面 结果 Vue使用Ref跨层级获取组件实例 ...

  3. Tensorflow-交叉熵&过拟合

    交叉熵 二次代价函数 原理 缺陷 假如我们目标是收敛到0.A点为0.82离目标比较近,梯度比较大,权值调整比较大.B点为0.98离目标比较远,梯度比较小,权值调整比较小.调整方案不合理. 交叉熵代价函 ...

  4. 一体化的Linux系统性能和使用活动监控工具–Sysstat

    [转]原文出处: Tecmint-Kuldeep Sharma   译文出处:Linux Story-天寒   欢迎分享原创到伯乐头条 在监控系统资源.系统性能和使用活动方面,Sysstat的确是一个 ...

  5. 通过 profiling 定位 golang 性能问题 - 内存篇 原创 张威虎 滴滴技术 2019-08-02

    通过 profiling 定位 golang 性能问题 - 内存篇 原创 张威虎 滴滴技术 2019-08-02

  6. hbuilder使用技巧总结

    HBuilder是DCloud(数字天堂)推出的一款支持HTML5的Web开发IDE.HBuilder的编写用到了Java.C.Web和Ruby.HBuilder本身主体是由Java编写,它基于Ecl ...

  7. valgrind和Kcachegrind性能分析工具详解

    一.valgrind介绍 valgrind是运行在Linux上的一套基于仿真技术的程序调试和分析工具,用于构建动态分析工具的装备性框架.它包括一个工具集,每个工具执行某种类型的调试.分析或类似的任务, ...

  8. 基于GTID恢复误篡改数据

    问题描述:创建测试库和测试表,先update数据,在delete数据,在update数据,通过gtid查找两次update的值. 参考文档:https://baijiahao.baidu.com/s? ...

  9. Docker安装Mycat并实现mysql读写分离,分库分表

    Docker安装Mycat并实现mysql读写分离,分库分表 一.拉取mycat镜像 二.准备挂载的配置文件 2.1 创建文件夹并添加配置文件 2.1.1 server.xml 2.1.2 serve ...

  10. Ajax(简介、基础操作、计算器,登录验证)

    Ajax简介 Ajax 即"Asynchronous Javascript And XML"(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术. ...