Mac下粗略实现ls命令

本文介绍了如何在Mac上粗略实现ls命令,包括解析命令行参数、读取目录结构和输出文件信息。通过创建main函数、命令行解析、获取文件详情等步骤,实现了基本的ls功能,如显示文件类型、所有者、组名、文件名和属性。文章还给出了Makefile的编写和结果演示。

在*nix系统下,ls命令都是一个很重要的命令,用于某一文件夹中的文件列表,这个命令实现起来其实也十分简单,今天闲来无事,就在Mac上粗略的写写代码。

文件结构

不关注内部细节,ls的主要实现应该包括两个部分,一部分是参数解析,一部分是输出文件列表,所以我的项目文件结构如下所示:
文件结构
main:程序入口点,调用输出列表函数
fileinfo:ls命令输出行model
parsecmd:该文件主要是参数解析部分,由于只是粗略的写写,并没有做什么检查,也只实现了ls中的-a和-l选项
dirhandler:该文件主要用于读取文件目录列表和文件相关信息。

main函数

首先来看看main函数中的代码,如下所示:

int main(int argc, const char * argv[])
{
    char* args = NULL;

    int argsCount = ParseCmds(argc, argv, args);

    int paths = argc - 1 - argsCount;
    if(paths == 0)
        showFilesWithArgs(".", args);
    else
        for(int i = argsCount + 1; i != argc; ++i)
        {
            if(paths > 1)
                printf("%s:\n", argv[i]);
            showFilesWithArgs(argv[i], args);
        }

    return 0;
}

首先,解析参数,结束之后,如果发现用户没有指定目录,则将目录设置为当前目录,否则循环输出每个目录的信息。

命令行解析

由于只是粗略的解析一下,所以命令行解析也只是简单的将命令字符分离出来,代码如下所示:

int ParseCmds(int argc, const char* argv[], char*& args)
{
    clean(args);

    args = (char*)malloc(sizeof(char) * ARGU_LEN);
    memset(args, 0, sizeof(char) * ARGU_LEN);

    int cmd_index = 0, argsCount = 0;
    for(int i = 1; i != argc; ++i)
    {
        const char* strp = *(argv + i);

        if((*strp) != '-')
            break ;

        ++argsCount;
        size_t arglen = strlen(strp);
        for(int j = 1; j != arglen; ++j)
            args[cmd_index++] = *(strp + j);
    }

    return argsCount;
}

首先清空参数数组,然后为参数数组分配足够的空间,命令行都是以「-」开头,所以如果不是以该字符开头的命令字符串就不是命令选项了,而应该是目录,就是说命令已经解析完毕。否则将字符一个一个的存到args数组当中。

输出结构字段

fileinfo包含ls输出的各个字段,如下所示:

typedef struct FileInfo
{
    int fileType;
    int numberOfLinks;
    unsigned long fileSize;
    int abbreviatedMonth;
    int abbreviatedDayOfMonth;
    int abbreviatedHour;
    int abbreviatedMin;
    char* ownerName;
    char* groupName;
    char* fileName;
    char* fileProp;
    FileInfo* nextFile;
}FileInfo;
  • fileType:该文件的类型,比如常规文件,目录,字符设备等
  • numberOfLinks:该文件所拥有的链接的数目
  • abbreviated*:该文件的最后修改日期
  • owerName:该文件的拥有者的名字
  • groupName:该文件的拥有者所在组的名字
  • fileName:该文件的名称
  • fileProp:该文件的属性字段
  • nextFile:用于保存下一个文件入口

输出文件列表

输出文件列表分为几个步骤:

  1. 读取目录结构,获取该目录下的所有文件名
  2. 读取各项文件的详细信息
  3. 填充fileinfo,组建属性字符串
  4. 根据命令行,输出相应结果

读取目录结构

*nix中提供了目录操作的相关函数,opendir, readdir, closedir,这三个函数用于打开目录入口,读取目录列表,关闭目录入口。读取目录列表将会返回一个dirent结构,描述目录的基本信息,代码如下所示:

if(chdir(path) == -1)
    {
        perror("chdir error");
        return ;
    }

    DIR* dir = opendir(path);
    if(dir == NULL)
    {
        perror("open dir error");
        return ;
    }

    struct dirent* _dirent = NULL;
    struct FileInfo* fileHeadPtr = NULL;
    while((_dirent = readdir(dir)) != NULL)
    {
        struct FileInfo* p = (struct FileInfo*)malloc(sizeof(struct FileInfo));
        p->fileName = (char*)malloc(sizeof(char) * (_dirent->d_namlen + 1));
        strcpy(p->fileName, _dirent->d_name);
        p->fileType = _dirent->d_type;

        if(fileHeadPtr == NULL)
        {
            fileHeadPtr = p;
            fileHeadPtr->nextFile = NULL;
        }
        else
        {
            p->nextFile = fileHeadPtr->nextFile;
            fileHeadPtr->nextFile = p;
        }

        if(args != NULL && strlen(args) != 0)
            getFileDetails(p);
    }

这段代码中主要做了4件事:
1. 读取并存储目录项信息
2. 构建目录链表
3. 保存文件名称及类型
4. 如果需要读取详细信息,则操作

getFileDetails

该函数主要用于获取目录项的详细信息,使用lstat函数来读取,该函数接收一个stat的结构,便于将详细信息存放在该结构中。关于stat结构请查看

获取基本信息

除了属性字符串需要特别组装以外,其他的信息都可以直接获取到,代码如下所示:

void getFileDetails(struct FileInfo*& info)
{
    struct stat detail;

    if(lstat(info->fileName, &detail) == -1)
    {
        perror("lstat error");
        return ;
    }

    info->numberOfLinks = detail.st_nlink;
    info->fileSize = detail.st_size;
    struct passwd* _passwd = getpwuid(detail.st_uid);
    info->ownerName = (char*)malloc(sizeof(char) * (strlen(_passwd->pw_name) + 1));
    strcpy(info->ownerName, _passwd->pw_name);
    struct group* _group = getgrgid(detail.st_gid);
    info->groupName = (char*)malloc(sizeof(char) * (strlen(_group->gr_name) + 1));
    strcpy(info->groupName, _group->gr_name);
    struct tm* _tm = localtime(&detail.st_mtimespec.tv_sec);
    info->abbreviatedMonth = _tm->tm_mon + 1;
    info->abbreviatedDayOfMonth = _tm->tm_mday;
    info->abbreviatedHour = _tm->tm_hour;
    info->abbreviatedMin = _tm->tm_min;

    info->fileProp = getFilePropStr(detail.st_mode);
    return ;
}

其中,根据stat结构中的st_uid 和 st_gid,分别使用getpwduid和getgrgid两个方法,可以分别从返回的passwd和group结构中获取到文件属主名和文件属主所在的组。根据其中的st_mtimespec.tv_sec字段,可以获取到该文件的时间相关信息。

组装文件属性

getFilePropStr接收一个mode_t类型的数据,返回一个ls命令中的格式化字符串,而mode_t在stat结构中也早已拿到,根据mode_t,我们可以获取到文件的类型,文件的属主,属组和其他人所拥有的权限,代码如下所示:

char* getFilePropStr(mode_t mode)
{
    char* prop = (char*)malloc(sizeof(char) * BUFSIZ);
    if(S_ISREG(mode))
        prop[0] = '-';
    else if(S_ISBLK(mode))
        prop[0] = 'b';
    else if(S_ISCHR(mode))
        prop[0] = 'c';
    else if(S_ISDIR(mode))
        prop[0] = 'd';
    else if(S_ISLNK(mode))
        prop[0] = 'l';
    else if(S_ISWHT(mode))
        prop[0] = 'w';
    else if(S_ISFIFO(mode))
        prop[0] = 'p';
    else if(S_ISSOCK(mode))
        prop[0] = 's';

    prop[1] = ((mode & S_IRUSR) == 0 ? '-' : 'r');
    prop[2] = ((mode & S_IWUSR) == 0 ? '-' : 'w');
    prop[3] = ((mode & S_IXUSR) == 0 ? '-' : 'x');
    prop[4] = ((mode & S_IRGRP) == 0 ? '-' : 'r');
    prop[5] = ((mode & S_IWGRP) == 0 ? '-' : 'w');
    prop[6] = ((mode & S_IXGRP) == 0 ? '-' : 'x');
    prop[7] = ((mode & S_IROTH) == 0 ? '-' : 'r');
    prop[8] = ((mode & S_IWOTH) == 0 ? '-' : 'w');
    prop[9] = ((mode & S_IXOTH) == 0 ? '-' : 'x');

    return prop;
}

输出列表

根据命令行,决定是短输出还是长输出,如代码所示:

void printLong(struct FileInfo* info)
{
    printf("%s %d %s %s %lu %d %d %d:%d %s\n", info->fileProp, info->numberOfLinks,
           info->ownerName, info->groupName, info->fileSize, info->abbreviatedMonth,
           info->abbreviatedDayOfMonth, info->abbreviatedHour, info->abbreviatedMin, info->fileName);
    return ;
}

void printShort(char* fileName)
{
    int printIndex = 0;
    bool bNeedTab = false;
    ++printIndex;
    printf(bNeedTab ? "    %s" : "%s", fileName), bNeedTab = true;
    if(printIndex % LS_ITEMS_PER_LINE == 0)
        printf("\n"), bNeedTab = false;

    return ;
}

至此,整个ls命令就编写完毕。

Makefile的编写

如果要在命令行下编译,我们知道,用makefile是相当不错的选择,虽然 只是寥寥几个文件,但一个make命令即能得到结果,也是不错的。其内容如下所示:

myls: main.cpp dirhandler.o parsecmd.o fileinfo.h
    g++ main.cpp dirhandler.o parsecmd.o -o myls
dirhandler.o: dirhandler.h dirhandler.cpp fileinfo.h
    g++ -c dirhandler.cpp fileinfo.h
parsecmd.o: parsecmd.h parsecmd.cpp
    g++ -c parsecmd.cpp

clean:
    rm dirhandler.o parsecmd.o fileinfo.h.gch

这样一来,只要在命令行中输入make,即可得到我们所需要的myls命令,输出make clean即可以清理在make过程中产生的杂碎文件。

结果演示

演示结果如图所示:

结果演示

额,似乎在最后做修改的时候,短输出的格式有点问题了这就尴尬了
不过,不要在意这些细节,那都是浮云。重点还是命令的实现过程,虽然十分简单,但做为练练手的项目,打发打发时间还是相当不错的。

代码链接

github下载

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值