Linux命令ls的自我实现(上)

简介: Linux命令ls的自我实现(上)

知识要点

1.Linux系统编程 目录和文件部分

2.C语言编程能力

任务说明

ls 是 GNU/Linux 操作系统中常见的命令. 请使用 C 语言,基于 GNU/Linux 操作系统,编程实现 ls 命令的 部分 功能.

验收要求

  • 实现 ls 的 -a、-l、-R、-t、-r、-i、-s 参数,并允许这些参数任意组合.
  • -R 需要通对 / 的遍历测试
  • 界面美观(输出对齐、与颜色显示等)
  • 无资源与内存泄露

参考资料

  • man 手册.
  • MichaelKerrisk.Linux/UNIX系统编程手册[M].北京:人民邮电出版社.
  • W.RichardStevens.Stephen.UNIX环境高级编程[M].第3版.戚正伟,译.北京:人民邮电出版社.
  • Unix/Linux编程实践教程

所要用到的函数

struct dirent,来自#include,diret是结构体中成员d_name用于存放文件名

struct dirent
{
     
    ino_t d_ino;
    off_t d_off;
    unsigned short d_reclen;
    char d_name[1];

在这里插入图片描述

  • opendir:打开目录文件
    函数返回指向目录流的指针。出错时,将返回 NULL,并正确设置 errno

readdir:读取目录条目,返回

closedir:关闭目录文件

stat: 得到文件信息,是在#include库里面的,
函数原型: int result =stat(char*fname,struct stat *bufp)
参数: fname 文件名 , bufp 指向buffer的指针
返回值: 遇到错误返回 -1,成功返回 0
在这里插入图片描述

ls初步简单实现(较为简单直接上代码)

   #include                                                                                                                                                                        
   #include
   #include
   #include 
   #define MAX_LEN 150      
     int g_leavelen = MAX_LEN;
     int g_maxlen;       
     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;
      struct dirent*direntp;
      if((dir_ptr=opendir(dirname))==NULL)     //打开失败
      {
    
        fprintf(stderr,"lsl:cannot open %s\n",dirname);
      }
      else                                   //打开成功
      {
    
         while((direntp=readdir(dir_ptr))!=NULL)  //依次读取目录,找到最大长度
          {
    
             if(direntp->d_name[0]!='.')
             {
    
               printf("%s  ",direntp->d_name);
             }
      }
        closedir(dir_ptr);
      }

ls -l的初步实现

所要的效果

在这里插入图片描述

分析以及所需要解决的问题

需要显示文件类型和许可权限,文件链接数,用户ID,所属组ID,所占空间大小,文件修改时间,文件名称。
我们要获取这些信息就需要,stat的联机帮助和/usr/include/sys/stat.h。
他们描述了struct stat 的成员变量
在这里插入图片描述
先简单使用一下,代码如下:

#include
#include
#include
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 1;
}
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);
  printf("    name: %s\n",fname);
}

在终端执行如下:
在这里插入图片描述
系统ls -l my_ls2.c 的结果如下:
在这里插入图片描述
我们用stat调出来的

  • 文件类型和许可权限是一串八进制代码
  • 用户ID和所属组ID都是一串数字
  • 显示的时间是时间戳

以上就是我们要实现 ls -l所需要解决的问题

将模式字段转换成字符

s_mode中的这串模式字段是一串八进制数字
把它传化为一个16位的二进制数
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过掩码把其他无关部分置为0,再与表示目录的代码比较,从而判断这是否是一个目录

更简单的方法就是用中的宏来代替上述代码:
在这里插入图片描述
在这里插入图片描述
根据上述,我们已经可以解码获得许可权限,代码如下:

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转化为字符串

我们可以通过库函数getpwuid来访问用户信息,如果用户信息保存在etc/passwd中,那么
getpwuid会查找/etc/passwd的内容,如果用户信息在NIS中,getpwuid会从NIS中获取信息。

getpeuid需要UID(user ID)作为参数,返回一个指向struct passwd的指针,这个结构定义在
/usr/include/pwd.h中如下:
在这里插入图片描述

所属组也有一个对应得结构体group和passwd类似
现在就可以实现将用户,所属组ID转化为字符串了
代码如下:

109   char*uid_to_name(gid_t uid)
  110 {
    
W>111   struct passwd*getpwuid();
  112   struct passwd*pw_ptr;
  113   static char numstr[10];                                                                                                             
E>114   if((pw_ptr=getpwuid(uid))==NULL)
  115   {
    
  116     sprintf(numstr,"%d",uid);
  117     return numstr;
  118   }
  119   else 
  120   {
    
  121     return pw_ptr->pw_name;
  122   }
  123 }
  124 char*gid_to_name(gid_t gid)
  125 {
    
W>126   struct group*getgrgid(),*grp_ptr;
  127   static char numstr[10];
E>128   if((grp_ptr=getgrgid(gid))==NULL)
  129   {
    
  130     sprintf(numstr,"%d",gid);
  131     return numstr;
  132   }
  133   else 
  134   {
    
  135     return grp_ptr->gr_name;
  136   }

最终ls -l代码实现以及效果

#include                                                                                                                    
     #include
     #include
     #include
     #include
     #include
     #include
     void do_ls(char[]);       //依次提取文件
     void dostat(char*);     
     void show_file_info(char*,struct stat*);  //打印文件信息
     void mode_to_letters(int ,char[]);
     char*uid_to_name(uid_t);    //将用户ID转化为字符串
     char*gid_to_name(gid_t);    //将所属组ID转化为字符串
     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;
      struct dirent*direntp;
      if((dir_ptr=opendir(dirname))==NULL)
        fprintf(stderr,"lsl:cannot open %s\n",dirname);
      else 
      {
     
             while((direntp=readdir(dir_ptr))!=NULL)
             {
    
                   if(direntp->d_name[0]!='.')         //判断是否是隐藏文件,若不是执行下一步
                   dostat(direntp->d_name);                                                                           
             }
        }
        closedir(dir_ptr);
      }
    }
    void dostat(char*filename)
    {
    
    struct stat info;
      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_to_name(),*ctime(),*git_to_name(),*filemode();
      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("%-8s ",uid_to_name(info_p->st_uid));                                                                                        
      printf("%-8s ",gid_to_name(info_p->st_gid));
      printf("%8ld ",(long)info_p->st_size);
      printf("%.12s ",4+ctime(&info_p->st_size));
      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';
     }
      char*uid_to_name(gid_t uid)
      {
    
        struct passwd*getpwuid();
        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;
       }
     }
      char*gid_to_name(gid_t gid)
      {
    
          struct group*getgrgid(),*grp_ptr;
          static char numstr[10];
       if((grp_ptr=getgrgid(gid))==NULL)
        {
    
          sprintf(numstr,"%d",gid);
          return numstr;
        }
       else 
       {
    
           return grp_ptr->gr_name;
       }
    }                          

效果展示:
在这里插入图片描述

ls -a(显示隐藏文件)

这一步骤实现起来很简单,隐藏文件就是文件名第一个字符是 . 的文件 比如 .git
我们在普通的ls 显示时都会有

 if(direntp->d_name[0]=='.')
        continue;

此步骤就是为了不显示隐藏文件
我们可以在执行到这一步时,对参数进行判断,查看是否有参数 -a 若有该参数跳过该步骤即可

ls -s(在文件左侧显示文件大小,以1024字节为块单位)

此步骤也极为简单,在结构体stat中存有该文件的所占字节的大小,st_size
我们只需要对该文件字节大小除以1024就可以得到所需显示的文件大小
但需要注意的是,当除以1024后得到的大小小于等于4且不等于0时,也需要显示4
实现起来也很简单,当检测到 -s 参数时执行下面代码即可:

  struct stat info;
           if(stat(filename,&info)==-1)
             perror(filename);
             long long size=info.st_size/1024;                              
               if(size<=4)           
                  printf("4   ");                
               else         
                  printf("%-4lld",size);                                                                                                                                                    

ls -i(输出文件的 i 节点的索引信息)

文件的索引信息也是保存在结构体stat中的st_ino中的,当检测到参数 -i 执行下面即可

    struct stat info;
    if(stat(filenames[j],&info)==-1)
       perror(filenames[j]);
     printf("%d  ",info.st_ino); 

ls -R(递归遍历目录)

思路讲解
我们可以先将该目录下的所有文件依次遍历打印出来,当打印完后,再次对该文件进行依次遍历,在此次遍历的过程中要对其文件的类型进行判断,若判断此文件为目录文件时,需要将此目录文件再次传入该函数,进行递归。
注意事项,当判断该文件为目录文件时,若要想在递归进入的函数中访问到该目录下的文件信息,我们应当传入该目录文件的路径,而不是目录名,此时我们就可以用到函数
sprintf

#include
#include
#include
#include
#include
void ls_R(char path[]);
int main()
{
    
  ls_R("/");
  return 0;
}
void ls_R(char path[])
{
    
  printf("%s:\n",path);
  DIR*dir_ptr;
  struct dirent*direntp;
  if((dir_ptr=opendir(path))==NULL)//打开目录
    fprintf(stderr,"lsl:cannot open %s\n",path);
  else 
  {
    
  if(direntp->d_name[0]=='.')
        continue;
    while((direntp=readdir(dir_ptr))!=NULL)//读取当前目录文件
    {
    
      printf("%s  ",direntp->d_name);
    }
  }
  printf("\n");
  closedir(dir_ptr);
  if((dir_ptr=opendir(path))==NULL)//打开目录
    fprintf(stderr,"lsl:cannot open %s\n",path);
  else 
  {
    
    while((direntp=readdir(dir_ptr))!=NULL)
    {
    
      if(direntp->d_name[0]=='.')
        continue;
      struct stat info;
      char temp[PATH_MAX];
      sprintf(temp,"%s/%s",path,direntp->d_name);
      if(lstat(temp,&info)==-1)
        perror(temp);
      if(S_ISDIR(info.st_mode))//判断是否为目录,如果是目录就进入递归
      {
    
        ls_R(temp);
      }
      
    }
  }
}
相关文章
|
5天前
|
缓存 监控 Linux
|
2天前
|
监控 Linux 开发者
如何在 Linux 中优雅的使用 head 命令,用来看日志简直溜的不行
`head` 命令是 Linux 系统中一个非常实用的工具,用于快速查看文件的开头部分内容。本文介绍了 `head` 命令的基本用法、高级用法、实际应用案例及注意事项,帮助用户高效处理文件和日志,提升工作效率。
14 7
|
1天前
|
Linux
在 Linux 系统中,`find` 命令是一个强大的文件查找工具
在 Linux 系统中,`find` 命令是一个强大的文件查找工具。本文详细介绍了 `find` 命令的基本语法、常用选项和具体应用示例,帮助用户快速掌握如何根据文件名、类型、大小、修改时间等条件查找文件,并展示了如何结合逻辑运算符、正则表达式和排除特定目录等高级用法。
10 5
|
3天前
|
监控 Linux
Linux常用命令-2
本文继续介绍Linux常用命令,涵盖目录操作、文件操作、系统信息和进程管理等类别。具体包括mkdir、rmdir、cp、mv、rm、touch、whereis、whatis、dmesg、free、date、cal、ps、kill、killall和top等命令的使用方法和常用参数。
27 7
|
2天前
|
监控 Linux Perl
Linux 命令小技巧:显示文件指定行的内容
在 Linux 系统中,处理文本文件是一项常见任务。本文介绍了如何使用 head、tail、sed 和 awk 等命令快速显示文件中的指定行内容,帮助你高效处理文本文件。通过实际应用场景和案例分析,展示了这些命令在代码审查、日志分析和文本处理中的具体用途。同时,还提供了注意事项和技巧,帮助你更好地掌握这些命令。
10 4
|
1天前
|
缓存 网络协议 Linux
Linux ip命令常用操作
Linux的 `ip`命令是一个强大且灵活的网络管理工具,能够执行从基本的网络接口配置到高级的路由和VLAN管理等多种操作。通过熟练掌握这些常用操作,用户可以更加高效地管理和配置Linux系统的网络环境。无论是在日常管理还是故障排除中,`ip`命令都是必不可少的工具。
6 2
|
2天前
|
缓存 运维 监控
【运维必备知识】Linux系统平均负载与top、uptime命令详解
系统平均负载是衡量Linux服务器性能的关键指标之一。通过使用 `top`和 `uptime`命令,可以实时监控系统的负载情况,帮助运维人员及时发现并解决潜在问题。理解这些工具的输出和意义是确保系统稳定运行的基础。希望本文对Linux系统平均负载及相关命令的详细解析能帮助您更好地进行系统运维和性能优化。
12 3
|
3天前
|
Linux Shell
Linux常用命令-1
本课程要求学生熟悉Linux系统终端窗口和命令基础,掌握文件目录类、系统信息类、进程管理类及其他常用命令,学时为3-6小时。课程内容涵盖Linux命令的特点、常见命令的使用方法及其应用场景,如文件浏览、目录切换、内容显示等。建议学生逐个操作命令并及时反馈问题。
27 5
|
6天前
|
缓存 Linux 开发者
深入理解Linux命令 `autom4te`
`autom4te` 是 GNU Autotools 中不可或缺的组件,通过高效处理 M4 宏,生成配置脚本并提供强大的调试功能。了解 `autom4te` 的工作机制和常用选项,可以帮助开发者更好地编写和维护配置文件,从而提高软件项目的配置和编译效率。在实际应用中,结合 `autoconf` 等工具,`autom4te` 能够为项目的构建过程提供坚实的基础。
15 2
|
7天前
|
Linux 开发工具
linux文本管理命令
本文档介绍了Linux系统中常用的文本处理命令,包括`echo`、`cat`、`head`、`tail`、`wc`、`less`、`grep`以及重定向符号的使用方法和练习题。此外,还详细讲解了VIM编辑器的特点、工作模式、常用快捷键和高级技巧,帮助用户高效地进行文本编辑和处理。
23 4