C语言实现Linux的ls命令

2020年第一篇正儿八经的博客,好好学习下Linux,用C语言实现Linux下的ls命令,很基础的知识。

基础知识

先看下DIR结构体、dirent结构体和stat结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct __dirstream
{
    void    *__fd;
    char    *__data;
    int      __entry_data;
    char    *__ptr;
    int      __entry_ptr;
    size_t   __allocation;
    size_t   __size;
    __libc_lock_define (, __lock)
};
typedef struct __dirstream DIR;

struct dirent {
    ino_t          d_ino;       /* inode number 索引节点号*/
    off_t          d_off;       /* offset to the next dirent 在目录文件中的偏移 */
    unsigned short d_reclen;    /* length of this record 文件名长*/
    unsigned char  d_type;      /* type of file; not supported by all file system types 文件类型 */
    char           d_name[256]; /* filename 文件名*/
};

struct stat {
        mode_t     st_mode;       //文件访问权限
        ino_t      st_ino;       //索引节点号
        dev_t      st_dev;        //文件使用的设备号
        dev_t      st_rdev;       //设备文件的设备号
        nlink_t    st_nlink;      //文件的硬连接数
        uid_t      st_uid;        //所有者用户识别号
        gid_t      st_gid;        //组识别号
        off_t      st_size;       //以字节为单位的文件容量
        time_t     st_atime;      //最后一次访问该文件的时间
        time_t     st_mtime;      //最后一次修改该文件的时间
        time_t     st_ctime;      //最后一次改变该文件状态的时间
        blksize_t st_blksize;    //包含该文件的磁盘块的大小
        blkcnt_t   st_blocks;     //该文件所占的磁盘块
};

目录操作相关函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//功能:打开一个目录; 返回值:成功则返回DIR*型态的目录流, 打开失败则返回NULL.
DIR* opendir(constchar * path);

//功能:循环读取dp中的文件和目录,每读取一个文件或目录都返回一个dirent结构体指针; 返回值:读取失败返回NULL
struct dirent* readdir(DIR* dp);

//功能:关闭参数dp所指的目录流; 返回值:关闭成功则返回0,,失败返回-1
int closedir(DIR*dp);

//功能:通过文件名file_name获取文件信息,并保存在buf所指的结构体stat中; 返回值:执行成功则返回0,失败返回-1
int stat(const char *file_name,struct stat *buf);

ls的C语言实现

先实现个最简单的V1.0版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>

int main(int argc, char *argv[]){

    DIR *dp;
    struct dirent *dirp;

    if(argc != 2){
        printf("usage: ls directory_name\n");
    }

   if((dp = opendir(argv[1])) == NULL){
        printf("can't open %s\n", argv[1]);
   }
   while((dirp = readdir(dp)) != NULL){
        printf("%s\n", dirp->d_name);
   }
   closedir(dp);
   exit(0);
}

封装一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>

void do_ls(char[]);

int main(int argc, char *argv[])
{
    if(argc==1)
        do_ls(".");
    do_ls(argv[1]);
        
    return 0;
}

void do_ls(char dirname[])
{
    DIR *dp;
    struct dirent *dirp;

    if((dp=opendir(dirname))==NULL)
        fprintf(stderr,"can't open %s\n",dirname);
    else
    {
        while((dirp=readdir(dp))!=NULL)
            printf("%s\n",dirp->d_name);
        closedir(dp);
    }
}

再实现个V2.0版本使用stat结构体可以列出每个文件详细信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>

//代码的报错处理没写好

//列出文件的信息
void show_stat_info(char *, struct stat *);

int main(int argc, char *argv[]){ 
    struct stat info;
    if(argc != 2){
        printf("usage: ls directory_name\n");
    }
    //do_ls(*argv[1]);

    if(stat(argv[1], &info) != -1){
        show_stat_info(argv[1],&info);
    }
    exit(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_mtime);
    printf("  group: %s\n", fname);
}

------------------
$./test2 test
  mode:  100755
 links:  1
  user:  501
  group: 20
   size: 12772
modtime: 1578118793
  group: test

实现个V3.0版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>

//代码的报错处理没写好

//列出目录下的所有文件
void do_ls(char[]);
//展示文件信息
void dostat(char *);
//列出文件的信息
void show_stat_info(char *, struct stat *);

int main(int argc, char *argv[]){ 
    if(argc==1)
        do_ls(".");
    do_ls(argv[1]);
        
    return 0;
}

void do_ls(char dirname[])
{
    DIR *dp;
    struct dirent *dirp;

    if((dp=opendir(dirname))==NULL)
        fprintf(stderr,"can't open %s\n",dirname);
    else
    {
        while((dirp=readdir(dp))!=NULL)
            //printf("%s\n",dirp->d_name);
            dostat(dirp->d_name);
        closedir(dp);
    }
}

void dostat(char *filename)
{
    struct stat info;
    if(stat(filename,&info)==-1)
        perror(filename);
    else
        show_stat_info(filename,&info);
}


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_mtime);
    printf("  group: %s\n", fname);
}

--------------------
$./test1 ./
  mode:  40700
 links:  63
  user:  501
  group: 20
   size: 2016
modtime: 1578125448
  group: .
  mode:  40755
 links:  145
  user:  501
  group: 20
   size: 4640
modtime: 1578125452
  group: ..
  mode:  40755
 links:  9
  user:  501
  group: 20
   size: 288
modtime: 1575381270
  group: rce-over-spark
  mode:  40755
 links:  7
  user:  501
  group: 20
   size: 224

现在可以写出最终版本了,把文件展示出来的信息修改成与ls一致的信息即可

这里需要补充两个结构体,是将user和group的数字形式转换成name形式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//pwd.h
struct passwd
{
  char *pw_name;                /* Username.  */
  char *pw_passwd;              /* Password.  */
  __uid_t pw_uid;               /* User ID.  */
  __gid_t pw_gid;               /* Group ID.  */
  char *pw_gecos;               /* Real name.  */
  char *pw_dir;                 /* Home directory.  */
  char *pw_shell;               /* Shell program.  */
};
//grp.h
struct group
{
    char *gr_name;              /* Group name.  */
    char *gr_passwd;            /* Password.    */
    __gid_t gr_gid;             /* Group ID.    */
    char **gr_mem;              /* Member list. */
};
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>
#include <grp.h>
#include <pwd.h>

//代码的报错处理没写好

//列出目录下的所有文件
void do_ls(char[]);
//展示文件信息
void dostat(char *);
//列出文件的信息
void show_file_info(char *, struct stat *);
//将文件属性修改成d/-,权限修改成rwx
void mode_to_letters(int ,char[]);
char * uid_to_name(uid_t);
char * gid_to_name(gid_t);

int main(int argc, char *argv[]){ 
    if(argc==1)
        do_ls(".");
    do_ls(argv[1]);
        
    return 0;
}

void do_ls(char dirname[])
{
    DIR *dp;
    struct dirent *dirp;

    if((dp=opendir(dirname))==NULL)
        fprintf(stderr,"can't open %s\n",dirname);
    else
    {
        while((dirp=readdir(dp))!=NULL)
            //printf("%s\n",dirp->d_name);
            dostat(dirp->d_name);
        closedir(dp);
    }
}

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(), *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("% -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_mtime));
    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(uid_t uid)
{
    struct passwd *getpwuid(), *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;
}

下一篇用C语言实现Linux的ps命令……

Ref

https://blog.nowcoder.net/n/9a48a7f697864e3bbdae2fe5bfcb0ef9

https://melville.club/posts/a-20190123-095755