先在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