0


模拟实现ls的总结与感悟

文章目录

  • 引言 我们每个人在学习Linux的时候使用的第一个命令都应该是ls,这个命令也开启了我们学习Linux的大门,但是应该很少有人知道ls是如何实现的,所以我便在自己的能力范围内对ls进行模拟实现

前期准备

argc与argv

我们在学习c语言的时候应该就应该对此有所了解了,其实main函数也是可以带参数的

argc,就是命令行所带的参数的个数,默认情况下argc是1
argv,就是命令行后面的字符串,默认情况下argv[0]=’.’

ls ...

ls后面带有两个参数,所以在1的基础上加上2
argv[0]=’.’,argv[1]=".",argv[2]=“…”

ls 

ls后面什么参数都没有,那么argc就是1
argv[0]="."

getopt

是一个用来解析命令参数的函数,就不用我们自己实现命令参数的解析

#include<unist.h>intgetopt(int argc,char*const argv[],constchar*optstring);

其中argc就是参数的个数,
argv里面就是命令行的所有参数,
而optstring就是我们要解析的参数如:“ali”,我们要解析的参数就是-a,-l,-i

失败的话就返回-1

unistd里有个 optind 变量,每次getopt后,这个索引指向argv里当前分析的字符串的下一个索引,因此argv[optind]就能得到下个字符串,通过判断是否以 '-'开头就可

stat

stat函数就是用来获取文件的信息
成功返回0,失败返回-1

include <sys/types.h>#include<sys/stat.h>#include<unistd.h>intstat(constchar*path,struct stat *buf)

path就是文件存储的路径,可以是相对路径也可以是绝对路径

struct stat结构体

struct stat
{
    dev_t     st_dev;/* ID of device containing file */文件使用的设备号
    ino_t     st_ino;/* inode number */    索引节点号 
    mode_t    st_mode;/* protection */  文件对应的模式,文件,目录等
    nlink_t   st_nlink;/* number of hard links */    文件的硬连接数  
    uid_t     st_uid;/* user ID of owner */    所有者用户识别号
    gid_t     st_gid;/* group ID of owner */   组识别号  
    dev_t     st_rdev;/* device ID (if special file) */ 设备文件的设备号
    off_t     st_size;/* total size, in bytes */ 以字节为单位的文件容量   
    blksize_t st_blksize;/* blocksize for file system I/O */ 包含该文件的磁盘块的大小   
    blkcnt_t  st_blocks;/* number of 512B 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 */ 最后一次改变该文件状态的时间   
};

struct stat中st_mode还有很多情况
我列举几个常见的

    S_IFMT   0170000    文件类型的位遮罩
    S_IFSOCK 0140000    套接字
    S_IFLNK 0120000     符号连接
    S_IFREG 0100000     一般文件
    S_IFBLK 0060000     区块装置
    S_IFDIR 0040000     目录
    S_IFCHR 0020000     字符装置
    S_IFIFO 0010000     先进先出
​
    S_ISUID 04000     文件的(set user-id on execution)位
    S_ISGID 02000     文件的(set group-id on execution)位
    S_ISVTX 01000     文件的sticky位
​
    S_IRUSR(S_IREAD)00400     文件所有者具可读取权限
    S_IWUSR(S_IWRITE)00200     文件所有者具可写入权限
    S_IXUSR(S_IEXEC)00100     文件所有者具可执行权限
​
    使用st_mode和S_IRGRP此类进行按位与&,如果为真就可以

    S_IRGRP 00040             用户组具可读取权限
    S_IWGRP 00020             用户组具可写入权限
    S_IXGRP 00010             用户组具可执行权限
​
    S_IROTH 00004             其他用户具可读取权限
    S_IWOTH 00002             其他用户具可写入权限
    S_IXOTH 00001             其他用户具可执行权限
​
    上述的文件类型在POSIX中定义了检查这些类型的宏定义:
    
    S_ISLNK (st_mode)    判断是否为符号连接
    S_ISREG (st_mode)    是否为一般文件
    S_ISDIR (st_mode)    是否为目录
    S_ISCHR (st_mode)    是否为字符装置文件
    S_ISBLK (s3e)        是否为先进先出
    S_ISSOCK (st_mode)   是否为socket
    若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是这个/tmp目录啦。
​

我们可以使用st_mode和 S_IFMT进行&,得到的来判断其的文件类型
同时还可以使用S_ISDIR(st_mode)此类函数判断其文件类型,

sprintf与fprintf

sprintf可以将格式化的数据存放到一个字符串里面

char fname[20];sprintf(fname,"%s \%s","hello","world");
fname=“hello \world”;

fprintf是将格式化的数据写入到一个文件流中
与printf的区别是printf是将内容写到stdout的标准输出流里面(即打印到显示屏上)

opendir && closedir

opendir就是打开一个目录,可以类比打开一个文件

#include<sys/types.h>#include<dirent.h>
  DIR* opendir (constchar* path );

path就是存放文件的路径,
而DIR 相当于FILE的指针
失败就返回一个NULL

DIR 结构体的原型为:struct_dirstream

struct __dirstream
   {void*__fd;/* `struct hurd_fd' pointer for descriptor.   */char*__data;/* Directory block.   */int __entry_data;/* Entry number `__data' corresponds to.   */char*__ptr;/* Current pointer into the block.   */int __entry_ptr;/* Entry number `__ptr' corresponds to.   */
     size_t __allocation;/* Space allocated for the block.   */
     size_t __size;/* Total valid data in the block.   */
     __libc_lock_define (, __lock)/* Mutex lock for this structure.   */};

closedir就是在每次调用完opendir后把目录关闭,避免内存泄漏

readdir

读取opendir目录返回值的列表,

#include<dirent.h>struct dirent*readdir(DIR* dir_handle);

返回dirent结构体的指针

struct dirent

{undefined

long d_ino;/* inode number 索引节点号 */

off_t d_off;/* offset to this dirent 在目录文件中的偏移 */unsignedshort d_reclen;/* length of this d_name 文件名长 */unsignedchar d_type;/* the type of d_name 文件类型 */char d_name [NAME_MAX+1];/* file name (null-terminated) 文件名,最长255字符 */}

实现过程中遇到的麻烦

颜色控制

printf("\033[31m hello \033[0m")
划线处就是控制颜色输出的格式,黑体字就是我们需要他是什么颜色

背景色 字体色

40: 黑 30: 黑

41: 红 31: 红

42: 绿 32: 绿

43: 黄 33: 黄

44: 蓝 34: 蓝

45: 紫 35: 紫

46: 深绿 36: 深绿

47: 白色 37: 白色
记得在打印完之后,把颜色恢复成NONE[^ 4],不然再后面的打印都会跟着变色。

其他的printf格式就由你们去探索吧

Linux中多文件操作

我们在早期写写代码的时候,可以就在一个文件上写就可以了,但是如果一旦代码量大的话,一个文件就显得太少了,且每次查找也十分麻烦,那么我们就应该在多个文件中,进行编写代码

但是在Linux中,多个文件可能有一点麻烦


  1. 全局变量的定义在.h文件中 最顶部加上这两行,ifndef后面的是任意都可以 不过下面define后要添加一样的内容,在最后加上#endif 之后声名全局变量,前加一个extern,不需要赋初值,在我们需要的.c文件中赋初值即可
#ifndef _LS_H_#define _LS_H_externint a;
……

#endif

在.c文件中赋初值

int a=0;//前面也要加变脸类型

代码实现

我们会实现ls 以及 -r -l -t -R -s -i -a

  • ls.h
#ifndef _LS_H_#define _LS_H_#include<stdio.h>#include<time.h>#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<dirent.h>#include<pwd.h>#include<grp.h>#include<string.h>#include<stdlib.h>externint aflag;externint lflag;//作为标识符,如果aflag lflag为1则有-a和-l这个参数,执行选项externint iflag;externint sflag;externint rflag;externint tflag;externint Rflag;externchar filename[256][260];externlong filetime[256];voidread_dir(char*dir);voidisFile(char*name,char*filename);voidprint(struct stat buf,char* filename);voiddisplay_rights(struct stat buf);voiddisplay_mode(struct stat buf);voiddisplay_sflag(char*fname,char*nname);voiddisplay_file(char*fname ,char*nname);//fname里面存放的是目录的路径,显示文件的信息voiddisplay_iflag(char*fname,char*nname);voidgetfilename(char* dir,int*cnt);voidsortbyletter(int cnt);voidgettime(int cnt,char* dir);//获得时间的数字voiddisplay_dir(char*dir);//显示目录下的所有文件,同时判断是否有-a选项,里面的是目录的路径名,有相对路径,也有绝对路径voidjudge_mode(int argc,char*argv[],int ch,char*s);voidIsFile(char* dir);voiddp_R(char*dir);#endif
  • ls.c
#include"ls.h"//打印颜色int aflag=0;int lflag=0;//作为标识符,如果aflag lflag为1则有-a和-l这个参数,执行选项int iflag=0;int sflag=0;int rflag=0;int tflag=0;int Rflag=0;char filename[256][260];long filetime[256];voidprint(struct stat buf,char* filename){if(S_ISREG(buf.st_mode))//一般文件{if(buf.st_mode&S_IXOTH)//可执行文件,打印成绿色{printf("\033[32m %s  \033[0m",filename);}else{printf("%s  ",filename);//直接接打印文件名,不显示文件}}elseif(S_ISDIR(buf.st_mode))//是一个目录打印蓝色{printf("\033[34m %s  \033[0m",filename);}}voiddisplay_rights(struct stat buf){if(buf.st_mode & S_IRUSR)printf("r");elseprintf("-");if(buf.st_mode & S_IWUSR)printf("w");elseprintf("-");if(buf.st_mode & S_IXUSR)printf("x");elseprintf("-");//组权限if(buf.st_mode & S_IRGRP)printf("r");elseprintf("-");if(buf.st_mode & S_IWGRP)printf("w");elseprintf("-");if(buf.st_mode & S_IXGRP)printf("x");elseprintf("-");//其他人权限if(buf.st_mode & S_IROTH)printf("r");elseprintf("-");if(buf.st_mode & S_IWOTH)printf("w");elseprintf("-");if(buf.st_mode & S_IXOTH)printf("x");elseprintf("-");}//打印文件类型voiddisplay_mode(struct stat buf){switch(buf.st_mode & S_IFMT)//s_ifmt是一个子域掩码,按位与的结果来判断是什么权限{case S_IFSOCK:printf("s");break;case S_IFLNK:printf("l");break;case S_IFREG:printf("-");break;case S_IFBLK:printf("b");break;case S_IFDIR:printf("d");break;case S_IFCHR:printf("c");break;case S_IFIFO:printf("p");break;}}voiddisplay_sflag(char*fname,char*nname){struct stat buf;//buf用来接收文件的各种信息stat(fname,&buf);if(stat(fname,&buf)==-1)//路径存放失败{perror("stat error\n");return;}longlong size= buf.st_size/1024;if(size<=4){printf("4 ");}else{printf("%4lld ",size);}print(buf,nname);}voiddisplay_file(char*fname ,char*nname)//fname里面存放的是目录的路径,显示文件的信息{struct stat buf;//buf用来接收文件的各种信息//   struct tm *t;//用来接收时间的参数,if(stat(fname,&buf)==-1)//路径存放失败{perror("stat error\n");return;}if(iflag)//有-i这个选项,就在首行打印{printf("%ld ",buf.st_ino);}if(sflag)//有-s这个选项{longlong size= buf.st_size/1024;if(size<=4){printf("%3d ",4);}else{printf("%3lld ",size);}}//打印文件类型display_mode(buf);//打印权限// user other groupdisplay_rights(buf);printf(" %2ld ",buf.st_nlink);//硬链接数//打印用户名和所属组printf("%10s ",getpwuid(buf.st_uid)->pw_name);printf("%10s ",getgrgid(buf.st_gid)->gr_name);printf(" %8ld  ",buf.st_size);//所占的字节大小char*ctime();printf("%.12s  ",4+ctime(&(&buf)-> st_mtime));print(buf,nname);//打印的同时显示颜色printf("\n");return;}//实现- i选项voiddisplay_iflag(char*fname,char*nname){struct stat buf;//buf用来接收文件的各种信息stat(fname,&buf);if(stat(fname,&buf)==-1)//路径存放失败{perror("stat error\n");return;}printf("%ld  ",buf.st_ino);// printf("%5s  ",nname);print(buf,nname);}//获取文件的名字voidgetfilename(char* dir,int*cnt){

    DIR * cntdir;struct dirent* cntitem;
    cntdir =opendir(dir);int len=0;while((cntitem=readdir(cntdir))!=NULL)//记录文件名,之后再进行文件名的字典序排序{strcpy(filename[*cnt],cntitem->d_name);
      len=strlen(filename[*cnt]);
      len=0;(*cnt)++;}}//按照字典序排列,默认按升序排列,如果有-r的话就逆序排列voidsortbyletter(int cnt){char temp[260];int j=0;int i=0;for(i=0;i<(cnt)-1;i++){for(j=0;j<(cnt)-1-i;j++){if(tflag){if(filetime[j]<filetime[j+1]){strcpy(temp,filename[j]);strcpy(filename[j],filename[j+1]);strcpy(filename[j+1],temp);}}if(rflag){if(strcmp(filename[j],filename[j+1])<0){strcpy(temp,filename[j]);strcpy(filename[j],filename[j+1]);strcpy(filename[j+1],temp);}}else{if(strcmp(filename[j],filename[j+1])>0){strcpy(temp,filename[j]);strcpy(filename[j],filename[j+1]);strcpy(filename[j+1],temp);}}}}}voidgettime(int cnt,char* dir)//获得时间的数字{struct stat buf;char fname[256];int j=0;sprintf(fname,"%s/%s",dir,filename[j]);stat(fname,&buf);for(j=0;j<(cnt);j++){

    filetime[j]=buf.st_mtime;}}voiddisplay_dir(char*dir)//显示目录下的所有文件,同时判断是否有-a选项,里面的是目录的路径名,有相对路径,也有绝对路径{int i=0;
    DIR *mydir;struct dirent *myitem;char fname[256];struct stat buf;//获取文件属性打印文件颜色if((mydir =opendir(dir))==NULL){perror("fail to opendir!\n");return;}int cnt=0;getfilename(dir,&cnt);//得到目录下文件的名字以及时间gettime(cnt,dir);sortbyletter(cnt);int j=0;for(j=0;j<cnt;j++){//fname里面是目录的名字和文件的名字,全都弄到fname里面sprintf(fname,"%s/%s",dir,filename[j]);//dir这个目录的路径名字,文件名,这些名字全都答应到fname这个字符串里面来接收stat(fname,&buf);if(filename[j][0]=='.'&& aflag==0)//没有-a参数,如果if条件成立的就继续下一次循环,否则往下执行{continue;//在目录往下继续搜索,遇到隐藏文件就跳过}if(lflag)//走到这里就说明有-a参数,ls -l -a dir,同时也有-l参数{//srvprintf(fname,"%s/%s",dir,myitem->d_name);//dir这个目录的路径名字,文件名,这些名字全都答应到fname这个字符串里面来接收//    printf("%s",fname);display_file(fname,filename[j]);//把文件的秘密打印出来,第一个是目录的路径名,第二个是里面的文件名                                      }elseif(Rflag)//出现了-R选项{//display_Rflag(fname,filename[j],buf);//fname里面是路径名,filename[j]里面都是文件}elseif(iflag)//出现了-i选项就执行{//sprintf(fname,"%s/%s",dir,myitem->d_name);//dir这个目录的路径名字,文件名,这些名字全都答应到fname这个字符串里面来接收display_iflag(fname,filename[j]);
            i++;if(i%5==0){printf("\n");}}elseif(sflag){display_sflag(fname,filename[j]);}else// ls -a dir,没有参数,只显示一个目录,{if(iflag){display_iflag(fname,filename[j]);}else{//如果是目录的话就用蓝色//如果是可执行文件的话就用绿色print(buf,filename[j]);}//   printf("%5s  ",filename[j]);// 显示文件名,}}printf("\n");closedir(mydir);}//判断是否有带什么参数voidjudge_mode(int argc,char*argv[],int ch,char*s){while((ch =getopt(argc,argv,s))!=-1)//getopt用来解析命令行的参数,返回int,错误就返回-1,解析-a和-l两个命令,getopt处理-开头的参数//每次getopt后,这个索引指向argv里当前分析的字符串的下一个索引,因此,optind也就往后移动//argv[optind]就能得到下个字符串//{switch(ch)//用ch来接收返回值{case'a': 
                        aflag =1;break;case'l': 
                        lflag =1;break;case'i': 
                        iflag =1;break;case's':
                        sflag=1;break;case'r'://递归显示文件,从根目录开始
                        rflag=1;break;case't':
                        tflag=1;break;case'R':
                        Rflag=1;break;default:printf("wrong option:%c\n",optopt);return;}}}voidisFile(char*name,char*filename){char fname[256];int ret =0;struct stat sb;
    ret =stat(name,&sb);if(S_ISDIR(sb.st_mode)){printf("%s: \n",name);if(ret ==-1){perror("stat error\n");return;}
    DIR*dp;struct dirent* sdp;
    dp=opendir(name);while(sdp=readdir(dp)){sprintf(fname,"%s/%s",name,sdp->d_name);stat(fname,&sb);if(strcmp(sdp->d_name,".")==0||strcmp(sdp->d_name,"..")==0||sdp->d_name[0]=='.'){continue;}print(sb,sdp->d_name);//  printf("%s ",sdp->d_name);}printf("\n");//是真就是目录//要考虑目录下还有目录read_dir(name);//        closedir(dp);}else{return;}//          //   printf("%s ",filename);//路径}voidread_dir(char*dir){char path[PATH_MAX];
      DIR *dp;struct dirent *sdp;
          dp =opendir(dir);if(dp ==NULL){perror("opendir error");return;}while(sdp =readdir(dp)){if(strcmp(sdp->d_name,".")==0||strcmp(sdp->d_name,"..")==0||sdp->d_name[0]=='.'){continue;}//读这个目录项,继续判断是不是目录,如果是目录iu还得再进入,如果是文件就打印//    //ifFile(sdp->d_name);//不能直接这样,要绝对路径才可以sprintf(path,"%s/%s", dir, sdp->d_name);isFile(path,sdp->d_name);}printf("\n");closedir(dp);return;}
  • main.c
#include"ls.h"intmain(int argc,char*argv[]){int ch,i;struct stat buf;//用来记录文件的信息//opterr = 0;//解析命令//用来解析命令行参数命令,控制是否向STDERR打印错误。为0,则关闭打印//optind默认是1,调用一次getopt就会+1// 判断是否带有参数 judge_mode(argc,argv,ch,"liasrtR");// 没有带参直接ls当前目录,后面没有参数,默认就是访问当前目录if(argc==1||*argv[argc-1]=='-')display_dir(".");for(i = optind; i < argc ; i++)//ls name1 name2....,从optind开始执行,执行后面的文件名字{if(stat(argv[i],&buf)==-1)//stat,判断的结果不成立,我们就直接返回-1,结束循环{// perror("cannot access argv[i]!\n");printf("cannot access %s!\n",argv[i]);printf(": No such file or directory\n ");return-1;}if(S_ISDIR(buf.st_mode))//是目录,判断此路径下有目录文件,里面的参数是st_mode{if(Rflag){IsFile(argv[i]);}printf("%s:\n",argv[i]);display_dir(argv[i]);//就对其进行打印, 对后面的参数,答应argv[i]代表的目录}else//file,为文件{if(lflag)//ls -l file,只打印一个{display_file(argv[i],argv[i]);//显示文件信息}else// ls file{print(buf,argv[i]);}}}printf("\n");return0;}

具体详细的代码可以查看我的github仓库
模拟实现ls.


标签: linux c语言 github

本文转载自: https://blog.csdn.net/m0_61567378/article/details/123596324
版权归原作者 时空旅客~ 所有, 如有侵权,请联系我们删除。

“模拟实现ls的总结与感悟”的评论:

还没有评论