这个学期上Linux课,老师布置了一项作业,要我们实现ls命令。不幸,我上课没有听讲,于是,下了几天苦工,先在网上找博客,看人家的例子,看到用到的C函数库函数,再用man 命令找它的用法,尝试着把整个流程弄懂。目前我实现的ls能做 ls 和 ls -l ,其他的功能只要知道了相应的API都不是难事了,重要的是学习的过程。
相关的系统C函数主要是 readdir,opendir,closedir
相关的结构体主要是 stat,dirent,winsize (获取终端的情况时用到)
ls:简要方式,仅显示文件 and/or 目录 的名字,分栏处理,文件名按列升序排序
(列升序排序:同一行,左边的名字比右边的名字字典序小;同一列,上边的名字比下边的名字字典序小。不少Linux和Unix系统中的 ls命令都是这样显示的)
ls -l:详细方式,每行显示一个文件/目录的详细信息。
依次为:类型,权限,用户名,组名,链接数,文件大小,修改时间,文件名。
两种方式下,以'.'开头的隐藏文件均不显示( 没有 -a 参数嘛 ! )
直接给出源码:
#include <grp.h>
#include <pwd.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void briefly(char*);
void amply(char*);
int cmp(const void*a , const void*b);
void print_type(struct stat e);
void print_auth(struct stat e);
void print_detail(struct stat e);
//用于文件名排序
int cmp(const void*a , const void*b ) {
return strcmp(*(char**)a,*(char**)b);
}
//简要方式 ls
void briefly(char path[]) {
DIR* dirp;
struct dirent *dp;
struct stat e;
//path不是合法目录,但有可能是文件
if (( (dirp=opendir(path)) == NULL)) {
if (lstat(path,&e) == -1) {
printf("\033[0;31mNo such file or directory\033[0m\n");
}
else {
char filename[256];
int tag = -1, c;
for(c = strlen(path) - 1 ; c >= 0 ; c --)
{
if(path[c] == '/') {
tag = c;
break;
}
}
int b = 0;
for(c = tag + 1 ; c < strlen(path) ; c ++)
filename[b++] = path[c];
printf("%s\n",filename);
}
return;
}
//path为目录
int cnt = 0,ter_width;
char * array[256];
//获取终端列数
struct winsize size;
if( isatty (STDOUT_FILENO) == 0)
exit(1);
if(ioctl(STDOUT_FILENO,TIOCGWINSZ,&size) < 0) {
printf("\033[0;31mioctl TIOCGWINSZ error\033[0m\n");
printf("\033[0;31mNO such file or directory\033[0m\n");
exit(1);
}
ter_width = size.ws_col;
//获取最长文件名, 对文件名排序
int max_len = 0;
while( (dp = readdir(dirp)) != NULL ) {
if(dp->d_name[0] != '.') {
int t = strlen(dp->d_name);
max_len = max_len < t ? t : max_len;
array[cnt++] = dp->d_name;
}
}
(void)closedir(dirp);
qsort(array,cnt,sizeof(array[0]),cmp);
//分栏显示
//显示colum : floor((ter_width+2)/(max_len+2))
//相邻两个文件名在终端显示的最小距离 : 2 space
//左右相邻的文件名在array中的offset : ceiling(cnt/colum)
int colum = (ter_width + 2)/(max_len + 2), offset;
offset = cnt%colum ==0 ? cnt/colum : cnt/colum+1;
//分栏输出与空格填补
int i,j,k,p;
for(i = 0 ; i < offset ; i ++)
{
j = i;
while ( j < cnt ) {
printf("%s",array[j]);
if(j + offset > cnt-1 ) {
printf("\n");
break;
}
k = 0;
p = max_len - strlen(array[j]) + 2;
while (k < p) {
printf(" ");
k ++;
}
j += offset;
}
}
}
//详细方式
void amply(char path[]) {
DIR* dirp;
struct dirent *dp;
struct stat e;
//判断为文件
if (((dirp=opendir(path)) == NULL)) {
if(lstat(path,&e) < 0) {
printf("\033[0;31mlstat error\033[0m\n");
printf("\033[0;31mNo such file or directory\033[0m\n");
return;
}
print_detail(e);
int c,tag = -1;
for(c = strlen(path) - 1 ; c >= 0 ; c --)
{
if(path[c] == '/') {
tag = c;
break;
}
}
//path数组加一个offset,因为不输出目录,只输出文件名
if(tag > 0)
path = path+tag+1;
printf("%s\n",path);
return;
}
//判断为目录
while( (dp = readdir(dirp)) != NULL ) {
if(dp->d_name[0] == '.') {
continue;
}
char PATH[256];
//拼接PATH为合法的相对路径
strcpy(PATH,path);
strcat(PATH,"/");
strcat(PATH,dp->d_name);
if(lstat(PATH,&e) < 0) {
printf("\033[0;31mlstat error\033[0m\n");
continue;
}
print_detail(e);
printf("%s\n",dp->d_name);
}
(void)closedir(dirp);
}
void print_detail(struct stat e) {
char e_time[32];
struct passwd* pw; //存储用户名
struct group * gp; //存储组名
//文件类型
print_type(e);
//文件权限 (所有者,同组其他用户,其他)
print_auth(e);
printf(" ");
//用户名和组名
pw = getpwuid(e.st_uid);
gp = getgrgid(e.st_gid);
printf("%-7s",pw->pw_name);
printf("%-7s",gp->gr_name);
//链接数
printf("%-4d",e.st_nlink);
//文件大小
printf("%-6d",(int)e.st_size);
//修改时间
strcpy(e_time, ctime(&e.st_mtime));
e_time[strlen(e_time)-1] = '\0';
printf("%-26s",e_time);
}
// 打印文件类型
void print_type(struct stat e) {
if (S_ISLNK(e.st_mode)) //是否为链接
printf("l");
else if (S_ISREG(e.st_mode)) //是否为文件
printf("-");
else if (S_ISDIR(e.st_mode)) //是否为目录
printf("d");
else if (S_ISCHR(e.st_mode)) //是否为字符设备文件
printf("c");
else if (S_ISBLK(e.st_mode)) //是否为块设备文件
printf("b");
else if (S_ISFIFO(e.st_mode)) //是否为FIFO
printf("f");
else if (S_ISSOCK(e.st_mode)) //是否为socket
printf("s");
}
void print_auth(struct stat e) {
//所有者权限
if(e.st_mode & S_IRUSR)
printf("r");
else
printf("-");
if(e.st_mode & S_IWUSR)
printf("w");
else
printf("-");
if(e.st_mode & S_IXUSR)
printf("x");
else
printf("-");
//同组用户权限
if(e.st_mode & S_IRGRP)
printf("r");
else
printf("-");
if(e.st_mode & S_IWGRP)
printf("w");
else
printf("-");
if(e.st_mode & S_IXGRP)
printf("x");
else
printf("-");
//其他
if(e.st_mode & S_IROTH)
printf("r");
else
printf("-");
if(e.st_mode & S_IWOTH)
printf("w");
else
printf("-");
if(e.st_mode & S_IXOTH)
printf("x");
else
printf("-");
}
int main(int argc, char*argv[]) {
//ls
if (argc == 1) {
briefly(".");
}
else if (strcmp(argv[1],"-l") == 0) {
//ls -l
if(argc == 2)
amply(".");
//ls -l ...
else {
for(int i = 2 ; i < argc ; i ++) {
amply(argv[i]);
}
}
}
//ls ...
else {
for(int i = 1 ; i < argc ; i ++)
briefly(argv[i]);
}
return 0;
}