这是我上操作系统这门课的习题,现在拿出来和大家分享一下。
欢迎大家和我的讨论与交流
/**
* Linux环境下ls命令的简单实现
* Copyright © 2011 Hang Studio. All rights reserved.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*输出的组间隔*/
#define SPACE 2
/*获取无符号整型数十进制数的长度*/
unsigned get_unsigned_len(unsigned n){
unsigned len = 0;
do{
++len;
n /= 10;
}while(n);
return len;
}
/* “字符串”结构体 */
typedef struct{
char *str;/*实际内容*/
unsigned length;/*字符串长度*/
}String;
typedef struct{
unsigned length;/*集合中文件名的数量*/
unsigned i,j;/*i是行数,j是列数*/
String *filenames;
unsigned maxlen;/*文件名集合中最大的文件名长度*/
char **formats;/*格式化输出序列*/
}FormatInfo;
/*预读目录root,将格式化输出的信息保存在info里*/
void preread(char *root, FormatInfo *info){
DIR *dir;
unsigned i,j,count = 0;
struct dirent *ptr;
String filename;
/*g是根据目录下的目录项名的长度进行分组的辅助变量*/
unsigned *g = NULL;
unsigned width = 0;
unsigned group = 0;
unsigned max = 0;
unsigned row = 1;
unsigned pos = 0;
struct winsize t_size;
ioctl(STDIN_FILENO, TIOCGWINSZ, &t_size);/*获取终端的行数和列数*/
if(info == NULL){
printf("Error! /"info/" can not be NULL!/n");
return;
}
dir = opendir(root);
if(dir == NULL){
perror("Fail to open dir./n");
exit(1);
}
while(readdir(dir)) ++count;/*统计文件和目录的个数*/
rewinddir(dir);/*回卷读取目录的指针*/
info->filenames = (String *)malloc(sizeof(String) * count);
g = (unsigned *)malloc(sizeof(unsigned) * count);
i = 0;
/*获取文件或目录名和计算其长度*/
while((ptr = readdir(dir)) != NULL){
filename.str = ptr->d_name;/*文件或目录名*/
filename.length = strlen(ptr->d_name);
info->filenames[i++] = filename;
}
/*对目录项根据目录名或文件名从小到大进行排序*/
/*此处使用选择法排序*/
for(i=0;i
for(j=i;j
if(strcmp(info->filenames[i].str,
info->filenames[j].str) > 0){
filename = info->filenames[i];
info->filenames[i] = info->filenames[j];
info->filenames[j] = filename;
}
}
}
/*用试探的方法测试最后列表的组数*/
group = count;/*先假定分为count个列表*/
row = 1;/*每列的行数为1*/
do{
for(j=0,width=0;j
for(i=0,max=0;i
/*找出每一列中文件或目录名最长的那一个*/
pos = j*row + i;
if(pos >= count) break;
if(info->filenames[pos].length > max){
max = info->filenames[pos].length;
g[j] = max;/*把每个列表的最大宽度存放到g数组里*/
}
}
/*
* 将各组最长的文件或目录名的长度相加
* 把列表之间的间隔也算上
*/
width += (max + SPACE);
}
/* 假如其总宽度大于终端的宽度(列数)
* 则把分组列表的数量减少,继续试探。
*/
if(width >= t_size.ws_col){
--group;
/* 这里是根据总数量和列表数量
* 推算出每个列表的最大行数
*/
row = (count%group)?(count/group+1):(count/group);
}else{
/*否则,估算列表的数量结束*/
break;
}
}while(1);
/*接下来组织每个列表的格式化输出字符串*/
info->formats = (char **)malloc(sizeof(char *) * group);
info->maxlen = 0;
for(i=0;i
if(g[i] > info->maxlen)
info->maxlen = g[i];/*这里找出最大文件或目录名的长度,后面有用*/
g[i] += SPACE;/*把列表之间的间隔算上*/
j = get_unsigned_len(g[i]) + 4;
info->formats[i] = (char *)malloc(sizeof(char) * j);
sprintf(info->formats[i],"%%-%us",g[i]);/*这里就是组织格式化输出字符串的关键部分*/
info->formats[i][j] = '/0';
}
putchar('/n');
info->length = count;
info->i = row;/*列表行数*/
info->j = group;/*列表数量*/
free(g);
closedir(dir);
}
void my_ls(char *root){
FormatInfo fi;
int i,j,pos,len;
char *filepath = NULL;
struct stat buf;
preread(root,&fi);/*预读目录*/
len = strlen(root);
filepath = (char *)malloc(sizeof(char) *
(strlen(root)+fi.maxlen+1));
filepath[0] = '/0';
strcat(filepath,root);
if(filepath[len-1] != '/'){
filepath[len++] = '/';/*此处仅考虑在Linux系统下*/
}
for(i=0,pos=0;i
for(j=0;j
pos = j*fi.i + i;
if(pos < fi.length){
filepath[len] = '/0';
strcat(filepath, fi.filenames[pos].str);
stat(filepath, &buf);
if(S_ISDIR(buf.st_mode)){
/*假如是目录则将其着色为蓝色加粗字体*/
printf("/033[01;34m");
}else{
/*否则按默认的颜色显示*/
printf("/033[0m");
}
printf(fi.formats[j],fi.filenames[pos]);
}else if(i
break;
}else{
putchar('/n');
return;
}
}
putchar('/n');
}
printf("/33[0m");/*还原着色设置*/
free(filepath);
}
int main(int argc, char **argv){
if(argc < 1){
printf("Wrong usage/n");
exit(1);
}else if(argc == 1){
my_ls(".");
}else{
my_ls(argv[1]);
}
return 0;
}
保存其为“my_ls.c”,编译成为“dir”的可执行文件
[hang@localhost test]$ gcc my_ls.c -o dir
列出我当前目录下的内容,如果编译的时候指定其输出的可执行文件在当前目录下的话,记得在运行的时候最好在其前面加上“./”哦
[hang@localhost test]$ ./dir ~/
运行结果如下:
当然,这代码还存在的不足,例如将输出的文件名列表重定向到一个文件时,它会把着色的终端转义字符也一起输入到里面去...