linux command "ls" simple implementation

先在octopress 写好的,然后直接拷过来的 :) http://linus-young.github.io/blog/2013/11/02/linux-command-ls-simple-implementation/

source code: github # multiple files

你也可以参考 unix v7 ls.c: code

功能

1. 支持五个参数选项,支持混合使用(如 -traRl )
*   `-t` -- 按照文件修改时间排序
*   `-r` -- 逆序输出
*   `-a` -- 不隐藏 . 开头的文件
*   `-R` -- 递归输出子目录的文件
*   `-l` -- 按行输出详细文件信息
2. 默认分栏输出, 默认按文件名排序 (大小写不敏感)
3. 支持指定一个或多个目录, 目录和参数选项不分先后顺序
* `myls ~`
* `myls ~ ..`
* `myls -atl . ~`
4. 颜色支持

目录用蓝色标出, 其余文件用绿色标出

HOW

1. 总体思路
  • 写之前拜读了一下 linux kernel coding style, tab的宽度设为8位也是由此而来。 另外基本上每行代码都不超过80 列。

  • 总的来说就是先存取文件名和文件信息到结构体中,然后根据不同的参数选项输出不同的信息。

    具体一点: 将文件的 name 和 info 保存在一个结构体 FileList 中,如果没有 -a 的参数, 就不收集 . 开头的文件。默认按字典序排序,若有 -t 选项则按文件的修改时间排序(新文件在前), 若有 -r选项则将结构体中存取的信息颠倒一下。 到输出的时候,若有 -R 选项则递归输出子目录下的信息,否则若有 -l 选项则输出详细信息。

2. 参数的处理

一开始打算手工处理,后来经Halftan提示使用了<unistd.h> 中的 getopt() 函数处理命令行选项,它是一个专门设计来减轻命令行处理负担的库函数。你可以查看IBM developerWorks的一篇博文:使用 getopt() 进行命令行处理

除了支持混合使用参数外,对于参数的扩展支持也是比较好的,只要修改 全局变量 char *optString,并在main函数里加入选项即可。

3. 输出的处理
  • 分栏

    分栏实现地比较简单,效果基本满足要求吧,先获得所有文件名的最大长度,再获得终端的列数,将列数除以 (最大长度 +1 )作为分的栏数。

  • 递归输出

    递归主要就是先输出 该目录 下的文件(判断是否有 -l调用不同的 display 函数 ), 然后遍历该目录,若文件是一个目录的话,重新获得 目录名,将子目录的文件保存到一个新的 FileList 结构体数组, 递归调用该函数本身(注意此处 . 和 .. 都是一个目录, 否则将进入死循环

  • 颜色支持

    本以为颜色的支持比较难,google了一下bash color code 还是比较简单的,只要修改 printf 函数就好了。基本的格式是 \e[34m , 其中 34 代表的是蓝色。你可以在这里获取其他颜色的表示:终端颜色显示

Bug

未使用 malloc 动态分配结构体数组,导致了处理长文件名时的一些错误。(可能文件名长度超过 80 就会报错了)

另外分栏输出在处理中文目录时显得很捉急,正确的方法可以参考Halftan神的代码:myls.c

myls.h

#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>

#define SORT_BY_MTIME (1<<0) /* -t */
#define REVERSE       (1<<1) /* -r */
#define ALL           (1<<2) /* -a */
#define RECURSIVE     (1<<3) /* -R */
#define DETAIL        (1<<4) /* -l */
#define MAX_FILE_COUNT   1024
#define MAX_FILENAME_LEN 200
#define MAX_PATH_LEN     500

struct FileList {
        char name[MAX_FILENAME_LEN]; /* filename, or subdirectory name */
        struct stat info;            /* file info                      */
} file_list[MAX_FILE_COUNT];


int  get_file_list(char dirname[], struct FileList *file_list, int mode);
void reverse_file_list(struct FileList *file_list, int count);

/* print functions */
void display(struct FileList *file_list, int count, int mode);
void display_file_simply(struct FileList *file_list, int count);
void display_file_detail(struct FileList *file_list, int count);
void display_file_recursively(struct FileList *file_list, int count, int mode);

/* compare functions used by qsort() */
int  name_cmp(const void *a, const void *b);
void lower_case(const char *filename, char *new_name);
int  mtime_cmp(const void *a, const void *b);

/* fileinfo help functions */
void file_mode_to_string(int mode, char str[]);
char *uid_to_name(uid_t uid);
char *gid_to_name(gid_t gid);

main.c

#include "myls.h"

/*
 * Simple implementation of linux command ls.
 *
 * myls supports the following command-line arguments:
 *   -t - sort by modification time
 *   -r - reverse output
 *   -a - do not hide entries starting with "."
 *   -R - list subdirectories recursively
 *   -l - use a long listing format
 * support mix of these options like -traRl
 * also support colors to distinguish dir and file :D
 *
 * FIXME: when comes to a filename very long(maybe >=80)
 *        it will get a floating point exception :(
*/

static const char *optString = "traRl";

int main(int argc, char *argv[])
/*
 * use getopt() to access command option.
 * you can use multiple options once like:
 * myls -atlrR
 * so convenient, just like real ls :)
 */
{
        int i;
        int mode = 0;       /* default no parameters */
        int opt = 0;        /* get command options */
        int file_count = 0; /* get the file count of a specefic directory. */

        while((opt = getopt(argc, argv, optString)) != -1) {
                switch(opt) {
                case 't': mode |= SORT_BY_MTIME; break;
                case 'r': mode |= REVERSE;       break;
                case 'a': mode |= ALL;           break;
                case 'R': mode |= RECURSIVE;     break;
                case 'l': mode |= DETAIL;        break;
                default:
                        exit(EXIT_FAILURE);
                }
        }

        if(optind == argc) { /* current directory */
                file_count = get_file_list(".", (struct FileList *)&file_list, mode);
                display((struct FileList *)&file_list, file_count, mode);
        }
        else {              /* specify one or more directories */
                for(i = optind; i < argc; ++i) {
                        if( optind + 1 != argc) /* more than one dir */
                                printf("%s: \n", argv[i]);
                        file_count = get_file_list(argv[i],
                                                   (struct FileList *)&file_list,
                                                   mode);
                        display((struct FileList *)&file_list, file_count, mode);
                }
        }

        return EXIT_SUCCESS;
}

int get_file_list(char dirname[], struct FileList *file_list, int mode)
/*
 * Store all the files under the dir in file_list,
 * sort them alphabetically default.
 * and return the count of files.
*/
{
        DIR           *dir_pointer;  /* the directory*/
        struct dirent *dirent_ptr;   /* each entry */
        int count = 0;
        char filename[MAX_FILENAME_LEN];

        if((dir_pointer = opendir(dirname)) == NULL) {
                fprintf(stderr, "ls: can not open %s\n", dirname);
                exit(EXIT_FAILURE);
        }
        else {
                /*
                 * change working directory, as stat() accesss relative path
                 * more reason can be found here: http://goo.gl/gO1zKd
                 */
                chdir(dirname);

                /* collect file name and info */
                while((dirent_ptr = readdir(dir_pointer)) != NULL) {
                        strcpy(filename, dirent_ptr->d_name);

                        if(filename[0] == '.' && !(mode & ALL))
                                continue;
                        strcpy(file_list[count].name, filename);

                        /*stats the file and fills in info. */
                        if(stat(filename, &file_list[count].info) == -1)
                                perror(filename);

                        ++count;
                }

                qsort(file_list, count, sizeof(file_list[0]), name_cmp);

                if(mode & SORT_BY_MTIME)
                        qsort(file_list, count, sizeof(file_list[0]), mtime_cmp);

                if(mode & REVERSE)
                        reverse_file_list(file_list, count);

                closedir(dir_pointer);
        }
        return count;
}

void display(struct FileList *file_list, int count, int mode)
/*
 * show file with specified mode(command option).
 */
{
        if(mode & RECURSIVE)
                display_file_recursively(file_list, count, mode);
        else if(mode & DETAIL)
                display_file_detail(file_list, count);
        else
                display_file_simply(file_list, count);
}

print.c

#include "myls.h"

void display_file_recursively(struct FileList *file_list, int count, int mode)
/*
 * display for -R option.
 * NOTE: for multiple directories, please use absolute path.
 */
{
        char path[MAX_PATH_LEN] = { 0 };
        char temp[MAX_PATH_LEN] = { 0 };
        char filename[MAX_FILENAME_LEN] = { 0 };
        struct FileList list[MAX_FILE_COUNT];
        int i = 0, size = 0;

        puts(get_current_dir_name()); /* absolute path */
        strcpy(path, get_current_dir_name());

        if(mode & DETAIL)
                display_file_detail(file_list, count);
        else
                display_file_simply(file_list, count);
        printf("\n");

        for(i = 0; i < count; ++i) {
                strcpy(filename, file_list[i].name);

                /* NOTE: "." and ".." is directory, skip them! */
                if((strcmp(filename, ".") == 0) || (strcmp(filename, "..") == 0))
                        continue;
                if(S_ISDIR(file_list[i].info.st_mode)) { /*if a directory*/
                        strcpy(temp, path);
                        strcat(path, "/");
                        strcat(path, filename); /*store absolute path*/
                        size = get_file_list(path, list, mode);
                        display_file_recursively(list, size, mode);
                        strcpy(path, temp);
                }
        }
}

void display_file_detail(struct FileList *file_list, int count)
/*
 * display for -l option.
 */
{
        char *uid_to_name(), *ctime(), *gid_to_name(), *filemode();
        char modestr[11];
        struct stat *info_p;
        int i;

        for(i = 0; i < count; ++i) {
                info_p = &file_list[i].info;

                file_mode_to_string(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));

                if(S_ISDIR(file_list[i].info.st_mode)) /* dir   show in blue  */
                        printf("\e[34m%s", file_list[i].name);
                else                                   /* other show in green */
                        printf("\e[92m%s", file_list[i].name);
                printf("\e[39m\n"); /* default white */
        }
}

void display_file_simply(struct FileList *file_list, int count)
/*
 * display without -R or -l option.
 */
{
        struct winsize size;
        int max_name_length = 0, i;
        int len = 0;
        int cols ; /*columns splited */

        for(i = 0; i < count; ++i) {
                len = strlen(file_list[i].name);
                if( len > max_name_length)
                        max_name_length = len;
        }

        /* get terminal size with ioctl (2) */
        ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
        cols = size.ws_col;
        cols /= (max_name_length + 1);

        for(i = 0; i < count; ++i) {
                if(i != 0 && (i % cols == 0))
                        puts(" ");
                /*
                 * use "%*s": the precision is not specified in the format
                 * string, but as an additional integer value argument
                 * preceding the argument that has to be formatted.
                 */
                if(S_ISDIR(file_list[i].info.st_mode))
                        printf("\e[34m%*s", -(max_name_length + 1),
                                file_list[i].name);
                else
                        printf("\e[92m%*s", -(max_name_length + 1),
                                file_list[i].name);
        }
        printf("\e[39m\n");
}

utility.c

#include "myls.h"

void reverse_file_list(struct FileList *file_list, int count)
{
        int i;
        char nametmp[MAX_FILENAME_LEN];
        struct stat infotmp;

        for(i = 0; i < count / 2; ++i) {
                strcpy(nametmp, file_list[i].name);
                strcpy(file_list[i].name, file_list[count - i - 1].name);
                strcpy(file_list[count - i -1].name, nametmp);
                infotmp = file_list[i].info;
                file_list[i].info = file_list[count - i - 1].info;
                file_list[count -i -1].info = infotmp;
        }
}


/* compare functions */
int name_cmp(const void *a, const void *b)
/*
 *      for qsort with filename.
 *      just like ls -- case insensitive
 */
{
        struct FileList *c = (struct FileList *) a;
        struct FileList *d = (struct FileList *) b;
        char cnametmp[MAX_FILENAME_LEN], dnametmp[MAX_FILENAME_LEN];

        lower_case(c->name, cnametmp);
        lower_case(d->name, dnametmp);

        return strcmp(cnametmp, dnametmp);
}

void lower_case(const char *filename, char *new_name)
/*
 * change filename to lower case for better sort.
 */
{
        int len = strlen(filename);
        int i;
        for(i = 0; i < len; ++i)
                new_name[i] = tolower(filename[i]);
        new_name[len] = '\0';
}

int mtime_cmp(const void *a, const void *b)
/*
 *      for qsort with modification time. default the newest first.
 */
{
        struct FileList *c = (struct FileList *) a;
        struct FileList *d = (struct FileList *) b;

        return d->info.st_mtime - c->info.st_mtime; /*compare time is so easy!*/
}


/* fileinfo help functions */
void file_mode_to_string(int mode, char str[])
/*
 * NOTE: It does not code setuid, setgid, and sticky codes.
 */
{
        strcpy(str, "----------");          /* default=no perms */

        if(S_ISDIR(mode))  str[0] = 'd';    /* directory?       */
        if(S_ISCHR(mode))  str[0] = 'c';    /* char devices     */
        if(S_ISBLK(mode))  str[0] = 'b';    /* block device     */

        if(mode & S_IRUSR) str[1] = 'r';    /* 3 bits for user  */
        if(mode & S_IWUSR) str[2] = 'r';
        if(mode & S_IXUSR) str[3] = 'r';

        if(mode & S_IRGRP) str[4] = 'r';    /* 3 bits for group */
        if(mode & S_IWGRP) str[5] = 'w';
        if(mode & S_IXGRP) str[6] = 'x';

        if(mode & S_IROTH) str[7] = 'r';    /* 3 bits for other */
        if(mode & S_IWOTH) str[8] = 'w';
        if(mode & S_IXOTH) str[9] = 'x';
}

char *uid_to_name(uid_t uid)
/*
 *      returns pointer to username associated with uid, uses getpwuid()
 */
{
        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)
/*
 *      returns pointer to group number gid. used getgrgid(3)
 */
{
        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;
}

Makefile

#
# Makefile of implementation of ls
#
all: myls

clean:
        rm -f myls

myls: main.c
        gcc -Wall main.c print.c utility.c -o myls -D_GNU_SOURCE


  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值