实现linux下 ls命令 (-a,-l,-R,-r,-i)

ls各个命令的功能

-a : 显示所有文件,连同隐藏文件一起列出来
-l : 一行只显示一个文件的详细信息
-R: 递归输出文件
-r : 逆序输出文件名
-i : 输出文件的 i 节点的索引信息

遇到的问题:

一. 需要用到的结构体:

DIR,dirent,passwd,group结构体

1.DIR结构体:

     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;  

DIR结构体类似于FILE,是一个内部结构体,而与之相关的函数有:

	#include
	#include
	DIR* opendir (const char * path );

打开一个目录
返 回 值:成功则返回DIR*型态的目录流, 打开失败则返回NULL

	#include
	#include
	struct dirent* readdir(DIR* dir);

需要循环读取dir中的文件和目录,每读取一 个文件或目录都返回一个dirent结构体指针

	#include 
	#include 
	int closedir(DIR* dir);

关闭参数dir所指的目录流
返 回 值:关闭成功则返回0,,失败返回-1
2. dirent结构体

目录文件(directory file)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针(摘自《UNIX环境高级编程(第二版)》)。
从定义能够看出,dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件,这就是证据。以下为dirent结构体的定义:

struct dirent   
{
        
  long d_ino; 					/* 索引节点号 */  
     
    off_t d_off; 				/* 在目录文件中的偏移 */  
     
    unsigned short d_reclen; 	/*文件名长 */  
     
    unsigned char d_type; 		/* 文件类型 */  
     
    char d_name [NAME_MAX+1]; 	/* 文件名,最长255字符 */  
}  

dirent同样也是起着一个索引的作用,如果想获得类似 ls -l 那种效果的文件信息,必须要靠stat函数了。
通过readdir函数读取到的文件名存储在结构体dirent的d_name成员中

3.passwd结构体:

#include 
#include 
struct passwd
{
     
	char *pw_name;                /* 用户登录名 */
	char *pw_passwd;              /* 密码(加密后) */
	__uid_t pw_uid;               /* 用户ID */
	__gid_t pw_gid;               /* 组ID */
	char *pw_gecos;               /* 详细用户名 */
	char *pw_dir;                 /* 用户目录 */
	char *pw_shell;               /* Shell程序名 */
};
  • struct passwd * getpwuid(uid_t uid);

    知道用户uid(user id),用getpwuid获取用户相关信息。
    此函数可用来获取用户名。
    

4 . group结构体

#include 
#include 
struct group
{
     
	char *gr_name;  				/* 组名 */
	char *gr_passwd;  			    /* 密码 */
	__gid_t gr_gid;  				/* 组ID */
	char **gr_mem;  				/* 组成员名单 */
}
  • struct group * getgrgid(gid_t gid);

    知道用户组gid(group id),用getgrgid获取用户组的相关信息。 用法:char * fileGrp =
    getgrgid(st.st_gid)->gr_name;

二. 需要用到的函数:

stat,opendir(),readdir()
1 stat函数:

  • 作用:获取文件信息

  • 头文件:
    #include
    #include
    #include

  • 函数原型:
    int stat(const char *path, struct stat *buf)

  • 返回值:成功返回0,失败返回-1

  • 参数:文件路径(名),struct stat 类型的结构体

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 */ 最后一次改变该文件状态的时间   
};

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     文件所有者具可执行权限
​
    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 的结构:

st_mode 主要包含了 3 部分信息:

15-12 位保存文件类型

11-9 位保存执行文件时设置的信息

8-0 位保存文件访问权限

st_atimest_mtimest_ctime的结构:
st_atime、st_mtime和st_ctime均表示自1970年1月1日00:00:00(国际标准时间)以来到时间记录所要表达的时间所经过的秒数,这种时间值称为日历时间。
若要转化成为人类可以识别的时间,运用以下函数:

#include 
struct tm *btime(const time_t *timer)

这个函数的功能就是把从1970年1月1日00:00:00到timer的秒数时间转换为可以人类可识别的时间。
返回值struct tm的结构为:

struct tm {
     
   int tm_sec;         /* 秒,范围从0到59			*/
   int tm_min;         /* 分,范围从0到59			*/
   int tm_hour;        /* 小时,范围从0到23		*/
   int tm_mday;        /* 月份的第几天,范围从1到31	*/
   int tm_mon;         /* 月份,范围从0到11		*/
   int tm_year;        /* 自1900起的年数			*/
   int tm_wday;        /* 一周中的第几天,范围从0到6*/
   int tm_yday;        /* 一年中的第几天,范围从0到365*/
   int tm_isdst;       /* 夏令时					*/    
};

三. S_ISDIR 、S_ISLNK等几个常见的宏
S_ISLNK(st_mode):是否是一个连接.
S_ISREG(st_mode):是否是一个常规文件.
S_ISDIR(st_mode):是否是一个目录
S_ISCHR(st_mode):是否是一个字符设备.
S_ISBLK(st_mode):是否是一个块设备
S_ISFIFO(st_mode):是否 是一个FIFO文件.
S_ISSOCK(st_mode):是否是一个SOCKET文件

我们使用最多的属性是st_mode,如果返回真,代表是目录…

2 opendir()

  • 头文件:# include # include

  • 函数原型:DIR* opendir (const char * name );
    (用来打开参数name 指定的目录, 并返回DIR*形态的目录流, 和open()类似, 接下来对目录的读取和搜索都要使用此返回值.)

  • 功能:打开一个目录,在失败的时候返回一个空的指针

DIR *opendir(const char *name),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:

struct dirent *readdir(DIR *dp);   
  
void rewinddir(DIR *dp);   
  
int closedir(DIR *dp);   
  
long telldir(DIR *dp);   
  
void seekdir(DIR *dp,long loc);  

3 readdir函数

  • 头文件:# include # include
  • 函数原型:struct dirent *readdir(DIR *dir);
  • 含义:readdir()返回参数dir 目录流的下个目录进入点

4.closedir函数

  • 头文件:# include # include
  • 函数原型:struct dirent *closedir(DIR *dir);
  • 含义:关闭dir流

其余用到的知识点

忽视 ctrl c 终止进程

signal(SIGINT, SIG_IGN);

实现函数:

代码还不是很完善,还需要改进

# include 
# include 
# include 
# include 
# include 
# include 
# include 
# include 
# include 
# include 
# include 
# include 
# include 

# define PARAM_NONE 0    // 无参数
# define PARAM_A 1       // -a: 显示所有文件,连同隐藏文件一起列出来
# define PARAM_L 2       // -l: 一行只显示一个文件的详细信息
# define PARAM_R 4       // -R: 递归输出
# define PARAM_r 8       // -r: 逆序输出文件名
# define PARAM_i 16      // -i: 输出文件的 i 节点的索引信息
# define MAXROWLEN 80    // 一行显示的最多字符数

int  g_leave_len = MAXROWLEN;    // 一行剩余长度,用于输出对齐
int g_maxlen;                    // 存放某目录下最长文件名的长度

// 错误处理函数,打印出错误所在行的行数和错误信息
void my_err(const char *err_string,int line)
{
     
    fprintf(stderr,"line:%d",line);      //stderr:标准错误输出设备(默认向屏幕输出)
    perror(err_string);                                                      
    exit(1);
}

// 获取文件属性并打印
void display_attribute(struct stat buf, char * name) 
{
     
    char buf_time[32];
    struct passwd *psd;    //从该结构体中获取文件所有者的用户名
    struct group *grp;     //从该结构体中获取文件所有者所属组的组名

    //获取文件属性并打印
    if (S_ISLNK(buf.st_mode)) {
     
        printf("l");
    } else if (S_ISREG(buf.st_mode)) {
     
        printf("-");
    } else if (S_ISDIR(buf.st_mode)) {
     
        printf("d");
    } else if (S_ISCHR(buf.st_mode)) {
     
        printf("c");
    } else if (S_ISBLK(buf.st_mode)) {
     
        printf("b");
    } else if (S_ISFIFO(buf.st_mode)) {
     
        printf("f");
    } else if (S_ISSOCK(buf.st_mode)) {
     
        printf("s");
    }

    //获取并打印文件所有者的权限
    if (buf.st_mode & S_IRUSR) {
     
        printf("r");
    } else {
     
        printf("-");
    }
    if (buf.st_mode & S_IWUSR) {
     
        printf("w");
    } else {
     
        printf("-");
    }
    if (buf.st_mode & S_IXUSR) {
     
        printf("x");
    } else {
     
        printf("-");
    }

    //获取并打印与文件所有者同组的用户对该文件的操作权限
    if (buf.st_mode & S_IRGRP) {
     
        printf("r");
    } else {
     
        printf("-");
    }
    if (buf.st_mode & S_IWGRP) {
     
        printf("w");
    } else {
     
        printf("-");
    }
    if (buf.st_mode & S_IXGRP) {
     
        printf("x");
    } else {
     
        printf("-");
    }

    //获取并打印其他用户对该文件的操作权限
    if (buf.st_mode & S_IROTH) {
     
        printf("r");
    } else {
     
        printf("-");
    }
    if (buf.st_mode & S_IWOTH) {
     
        printf("w");
    } else {
     
        printf("-");
    }
    if (buf.st_mode & S_IXOTH) {
     
        printf("x");
    } else {
     
        printf("-");
    }

    printf("    ");

    //根据 uid 与 gid 获取文件所有者的用户名与组名
    psd = getpwuid(buf.st_uid);
    grp = getgrgid(buf.st_gid);
    printf("%4d ",buf.st_nlink);  //打印文件的链接数 
    printf("%-8s ",psd->pw_name);
    printf("%-8s ",grp->gr_name);

    printf("%6d",buf.st_size);
    strcpy(buf_time, ctime(&buf.st_mtime));
    buf_time[strlen(buf_time) - 1] = '0';    // 去掉换行符
    printf(" %s",buf_time);    //打印文件的时间信息
}

// 在没有使用-1选项时,打印一个文件名,打印时上下行对齐
void display_single(char *name)
{
     
    int i, len;

    //如果本行不足以打印一个文件名则换行
    if (g_leave_len < g_maxlen) {
     
        printf("\n");
        g_leave_len = MAXROWLEN;
    }

    len = strlen(name);
    len = g_maxlen - len;
    printf("%-s",name);

    for (i=0; i<len; i++) {
     
        printf(" ");
    }
    printf(" ");
    //下面的 2 指空两格
    g_leave_len -= (g_maxlen + 2);
}

void display_i(char *name)
{
     
    struct stat buf;

    if (lstat(name, &buf) == -1)
    {
     
        my_err("stat", __LINE__);
    }

    printf("%-9ld %s",buf.st_ino,name);
    printf("\n");
}

/*
*   根据命令行参数和完整路径名显示目标文件
*   参数flag:命令行参数
*   参数pathname:包含了文件名的路径名
*/
void display(int flag, char * pathname)
{
     
    int i, j;
    struct stat buf;
    char name[NAME_MAX +1];

    //从路径中解析出文件名
    for (i=0, j=0; i<strlen(pathname); i++) {
     
        if (pathname[i] == '/') {
     
            j = 0;
            continue;
        }
        name[j++] = pathname[i];
    }
    name[j] = '\0';

    //用 lstat 而不是 stat 以方便解析链接文件
    if ( lstat(pathname, &buf) == -1) {
     
        my_err("stat",__LINE__);
    }

    switch (flag) {
     
        case PARAM_NONE:  //没有选项
            if (name[0] != '.') {
     
                display_single(name);
            }
            break;
        
        case PARAM_A:  //-a: 显示包括隐藏文件在内的所有文件
            display_single(name);
            break;
        
        case PARAM_L:  //-l: 每个文件单独占一行,显示文件详细属性信息
            if (name[0] != '.') {
     
                display_attribute(buf, name);
                printf(" %-s\n",name);
            }
            break;
        
        case PARAM_A + PARAM_L:  //同时有-l和-a选项的情况
            display_attribute(buf, name);
            printf(" %-s\n",name);
            break;

        case PARAM_R:
            if(name[0] != '.') {
     
                display_single(name);
            }
            break;
        
        case PARAM_R + PARAM_A:
            display_single(name);
            break;
            
        case PARAM_A + PARAM_L + PARAM_R:
            display_attribute(buf,name);
            printf(" %-s\n",name);
            break;
            
        case PARAM_L + PARAM_R:
            if(name[0] != '.') {
     
                display_attribute(buf,name);
                printf(" %-s\n",name);
            }
            break;

        case PARAM_r:
            if(name[0] != '.') {
     
                display_single(name);
            }
        break;

        case PARAM_i:
            if(name[0] != '.') {
     
                display_i(name);
            }

        default:
            break;
    }
}

void display_dir(int flag_param, char *path)
{
     
    DIR *dir;
    struct dirent *ptr;                                                                      //struct dirent
    int count = 0;
    char filenames[256][PATH_MAX+1],temp[PATH_MAX+1];

    //获取该目录下文件总数和最长的文件名
    dir = opendir(path); 
    
    if (dir == NULL) {
     
        my_err("opendir",__LINE__);
    }

    while ((ptr = readdir(dir)) != NULL) {
        // readdir()读取opendir 返回值的那个列表
        if (g_maxlen < strlen(ptr->d_name)) {
     
            g_maxlen = strlen(ptr->d_name);
        }
        count++;
    }
    closedir(dir);

    if(count>256)
        my_err("too many files under this dir",__LINE__);

    int i, j, len = strlen(path);
    
    //获取该目录下所有的文件名
    dir = opendir(path);
    
    for(i = 0; i < count; i++) {
     
        ptr = readdir(dir);
        if( ptr == NULL ) {
     
            my_err("readdir",__LINE__);
        }
        strncpy(filenames[i],path,len);
        filenames[i][len] = '\0';
        strcat(filenames[i],ptr->d_name);
        filenames[i][len+strlen(ptr->d_name)] = '\0';
    }

    //使用冒泡法对文件名进行排序,排序后文件名按字母顺序存储于filenames
    if(flag_param & PARAM_r) {
     
        for(i = 0; i < count - 1; i++) {
     
            for(j = 0; j < count - 1 - i; j++) {
     
                if(strcmp(filenames[j], filenames[j+1]) < 0) {
     
                    strcpy(temp, filenames[j + 1]);
                    temp[strlen(filenames[j+1])] = '\0';
                    strcpy(filenames[j+1], filenames[j]);
                    filenames[j+1][strlen(filenames[j])] = '\0';
                    strcpy(filenames[j], temp);
                    filenames[j][strlen(temp)] = '\0';
                }
            }
        }
    } else {
     
        for(i = 0; i < count-1; i++) {
     
            for(j = 0; j < count-1-i; j++) {
     
                if( strcmp(filenames[j],filenames[j+1]) > 0 ) {
     
                    strcpy(temp,filenames[j+1]);
                    temp[strlen(filenames[j+1])] = '\0';
                    strcpy(filenames[j+1],filenames[j]);
                    filenames[j+1][strlen(filenames[j])] = '\0';
                    strcpy(filenames[j],temp);
                    filenames[j][strlen(temp)] = '\0';
                }
            }
        }
    }

    for (i = 0; i < count; i++)
        display(flag_param, filenames[i]);

    if(flag_param & PARAM_R) {
       
        rewinddir(dir);

        for(i = 0; i < count-1; i++) {
     
            for(j = 0; j < count-1-i; j++) {
     
                if(strcmp(filenames[j],filenames[j+1]) > 0) {
     
                    strcpy(temp,filenames[j+1]);
                    temp[strlen(filenames[j+1])] = '\0';
                    strcpy(filenames[j+1],filenames[j]);
                    filenames[j+1][strlen(filenames[j])] = '\0';
                    strcpy(filenames[j],temp);
                    filenames[j][strlen(temp)] = '\0';
                }
            }
        }

        while( (ptr=readdir(dir)) != NULL)
        {
     
            char path_R[PATH_MAX+1];
            strncpy(path_R,path,sizeof(path));
            path_R[strlen(path_R)+1]='\0';
            strcat(path_R,ptr->d_name);
        //    printf("%s ",ptr->d_name);       重复打印一遍目录里的全部文件 
        //    printf("%s ",path_R);            重复打印一遍目录里的全部文件的路径
            int q = strlen(path_R);

            path_R[q]='\0';
            struct stat buf_R;
            lstat(path_R,&buf_R);

            if(ptr->d_name[0]!=46 && ptr->d_name[1]!=46 && S_ISDIR(buf_R.st_mode)) {
     
                printf("\n");
                char path_R[PATH_MAX+1];
                strncpy(path_R,path,sizeof(path));

                path_R[strlen(path_R)+1]='\0';
                strcat(path_R,ptr->d_name);
                int x = strlen(path_R);

                path_R[x]='/';
                path_R[x+1]='\0';

                printf("%s\n",path_R);
                display_dir(flag_param,path_R);
            }
        }
    }
    
    closedir(dir);

    //如果命令行中没有-l选项,打印一个换行符
    if ( (flag_param & PARAM_L) == 0 )
        printf("\n");
}

int main(int argc, char ** argv)
{
     
    signal(SIGINT, SIG_IGN);
    int i, j, k, num;
    char path[PATH_MAX+1];
    char param[32];   //保存命令行参数,目标文件名和目录名不在此列
    int flag_param = PARAM_NONE;   //参数种类,即是否有-l和-a选项
    struct stat buf;

    //命令行参数的解析,分析-l,-a,-al......选项
    j = 0,
    num = 0;
    for (i=1; i<argc; i++) {
     
        if (argv[i][0] == '-') {
     
            for (k=1; k < strlen(argv[i]); k++,j++) {
     
                param[j] = argv[i][k];
            }
        num++;
        }
    }

    //只支持参数a,l,r,R,i,如果含有其他选项就报错
    for (i=0; i<j; i++) {
     
        if (param[i] == 'a') {
     
            flag_param |=PARAM_A;
            continue;
        } else if (param[i] == 'l') {
     
            flag_param |=PARAM_L;
            continue;
        } else if (param[i] == 'R') {
     
            flag_param |=PARAM_R;
            continue;
        } else if (param[i] == 'r') {
     
            flag_param |=PARAM_r;
            continue;
        } else if (param[i] == 'i') {
     
            flag_param |=PARAM_i;
            continue;
        } else {
     
            printf("my_ls: invalid option -%c\n",param[i]);
            exit(1);
        }
    }
    param[j] = '\0';

    //如果没有输入文件名或目录,就显示当前目录
    if ((num + 1) == argc) {
     
        strcpy(path, "./");
        path[2] = '\0';
        display_dir(flag_param,path);
        return 0;
    }

    i = 1;
    do {
     
        //如果不是目标文件名或目录,解析下一个命令行参数
        if (argv[i][0] == '-') {
     
            i++;
            continue;
        } else {
     
            strcpy(path, argv[i]);

            //如果目标文件或目录不存在,报错并退出程序
            if ( stat(path, &buf) == -1 )
                my_err("stat",__LINE__);
            
            if ( S_ISDIR(buf.st_mode) ) {
         //argv[i]是一个目录
                //如果目录的最后一个字符不是'/',就加上'/'
                if( path[ strlen(argv[i])-1 ] !='/' ) {
     
                    path[ strlen(argv[i]) ] = '/';
                    path[ strlen(argv[i])+1 ] = '\0';
                }
                else
                    path[ strlen(argv[i]) ] = '\0';

                display_dir(flag_param,path);
                i++;
            }
            else {
           //argv[i]是一个文件
                display(flag_param, path);
            }
            i++;
        }
    } while (i<argc);

    return 0;
}

你可能感兴趣的:(实现linux下 ls命令 (-a,-l,-R,-r,-i))