在*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:用于保存下一个文件入口
输出文件列表
输出文件列表分为几个步骤:
- 读取目录结构,获取该目录下的所有文件名
- 读取各项文件的详细信息
- 填充fileinfo,组建属性字符串
- 根据命令行,输出相应结果
读取目录结构
*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过程中产生的杂碎文件。
结果演示
演示结果如图所示:
额,似乎在最后做修改的时候,短输出的格式有点问题了
不过,不要在意这些细节,那都是浮云。重点还是命令的实现过程,虽然十分简单,但做为练练手的项目,打发打发时间还是相当不错的。
本文介绍了如何在Mac上粗略实现ls命令,包括解析命令行参数、读取目录结构和输出文件信息。通过创建main函数、命令行解析、获取文件详情等步骤,实现了基本的ls功能,如显示文件类型、所有者、组名、文件名和属性。文章还给出了Makefile的编写和结果演示。
3242

被折叠的 条评论
为什么被折叠?



