ls命令是linux下最常用的命令之一,它的使用很简单,可是功能却很多,有很多的参数,这里我们就自己写一个ls命令,实现ls基本的功能,并添加-a和-l的实现。
ls -a 不隐藏任何以. 开始的项目
ls -l 使用较长格式列出详细信息
在这之前我们先介绍几个在实现ls过程中使用的函数。
如果参数是一个目录:
首先要介绍一下DIR与dirent两个结构体:
struct __dirstream
{
void *__fd;
char *__data;
int __entry_data;
char *__ptr;
int __entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (, __lock)
};
typedef struct __dirstream DIR;
DIR结构体类似于FILE,是一个内部结构体,而与之相关的函数有:
#include<sys/types.h>
#include<dirent.h>
DIR* opendir (const char * path );
打开一个目录
返 回 值:成功则返回DIR*型态的目录流, 打开失败则返回NULL.
DIR结构体;
#include<sys/types.h>
#include<dirent.h>
struct dirent* readdir(DIR* dp);
需要循环读取dp中的文件和目录(对,包括目录),每读取一 个文件或目录都返回一个dirent结构体指针
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR*dp);
关闭参数dp所指的目录流
返 回 值:关闭成功则返回0,,失败返回-1
接着是dirent结构体,首先我们要弄清楚目录文件(directory file)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针(摘自《UNIX环境高级编程(第二版)》)。从定义能够看出,dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件,这就是证据。以下为dirent结构体的定义:
struct dirent
{
long d_ino; /* 索引节点号 */
off_t d_off; /* 在目录文件中的偏移 */
unsigned short d_reclen; /*文件名长 */
unsigned char d_type; /* 文件类型 */
char d_name [NAME_MAX+1]; /* 文件名,最长255字符 */
}
dirent同样也是起着一个索引的作用,如果想获得类似 ls -l 那种效果的文件信息,必须要靠stat函数了。
通过readdir函数读取到的文件名存储在结构体dirent的d_name成员中
stat/lstat函数 函数原型:
//! 需要包含de头文件
#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
这两个函数功能基本相同,都是获得文件的属性,区别在于如果文件是符号链接stat返回的是符号链接指向文件的属性,而lstat返回的是符号链接本身的属性。(是不是觉得有点难理解,你可以这样想,lstat多一个l,所以比stat牛一点他就来处理link)
path为文件路径,buf为返回的状态,类型为struct stat
即获取文件名为path的文件信息,保存在buf所指结构体中执行成功返回0,失败返回-1
结构体内容为:
struct stat
{
dev_t st_dev; /*文件所在设备的ID*/
ino_t st_ino; /* inode节点号*/
mode_t st_mode; /* 文件的类型和存取的权限*/
nlink_t st_nlink; /* 链向此文件的连接数(硬连接)*/
uid_t st_uid; /* user id*/
gid_t st_gid; /* group id*/
dev_t st_rdev; /* 设备号,针对设备文件*/
off_t st_size; /* 文件大小,字节为单位*/
blksize_t st_blksize; /* blocksize for filesystem I/O -系统块的大小*/
blkcnt_t st_blocks; /* 文件所占块数*/
time_t st_atime; /* 最近存取时间*/
time_t st_mtime; /*最近修改时间*/
time_t st_ctime; /* 最后状态改变时间 */
};
这个结构体中成员st_mode用来表示文件的类型和文件的权限
尔st_mode是用特征位来表示文件类型的,大家可以去了解一下,我用的是一个更为简便的办法,文件类型在POSIX中定义了检查这些类型的宏定义:
S_ISLINGK(st_mode) 判断是否位符号链接
S_ISREG(st_mode) 是否为一般文件
S_ISDIR(st_mode) 是否为目录
S_ISCHR(st_mode) 是否位字符装置文件
S_ISBLK(s3e) 是否先进先出
S_ISSOCK(st_mode) 是否为socket
而在
S_IRUSR 用户r权限
S_IWUSR 用户w权限
S_IXUSR 用户x权限
S_IRGRP 用户组r权限
S_IWGRP ~
S_IXGRP ~
S_IROTH 其他r权限
S_IWOTH ~
S_IXOTH ~
只要与buf.st_mode做一下 & 操作
例:
#include<stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
struct stat buf;
stat("/home", &buf);
if(S_IXUSR&buf.st_mode)
printf("x\n");
return 0;
}
输出结果为’x’
而至于获取用户名和组名则:
#include<stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
int main ()
{
struct stat buf;
stat("/home/lzj/usual",&buf);
struct passwd *passwd;
passwd = getpwuid (buf.st_uid);
printf ("User:\t%s\n", passwd->pw_name);
struct group *group;
group = getgrgid (buf.st_gid);
printf ("Group:\t%s\n", group->gr_name);
}
输出结果为
User: LZJ
Goup: LZJ
如果改变stat函数中的路径名为/home结果为
User: root
Group: root
其中getpwuid()和getgrgid()函数是用来获取用户名和用户组名的
getpwuid()函数通过用户的uid查找用户的passwd数据,出错返回空指针,并设置erron的值
getgrgid()函数用来依参数gid指定的组识别码逐一搜索文件,找到时将该组文件以group结构体返回,返回NULL表示已无数据或有错误发生
我在写代码的过程
还遇到了一个函数ctime()
它的函数原型为char *ctime(const time_t *time);
他的功能是把日期和时间转换成字符串
/*************************************************************************
> File Name: ls.c
> Author:
> Mail:
> Created Time: 2017年07月18日 星期二 17时11分49秒
****************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<unistd.h>
#include<sys/types.h>
#include<dirent.h>
#include<grp.h>
#include<pwd.h>
#include<errno.h>
#include<sys/stat.h>
#include<limits.h>
#define MAXROWLEN 80 //一行显示的最多字符数
void error(char *p,int line) //错误处理函数
{
printf("error !:%d",line);
perror(p);
exit(1);
}
void take_out(char *path,char *name) //从路径名中解析出文件名
{
int i,j;
for(i=0,j=0;i<strlen(path);i++)
{
if(path[i] == '/')
{
j=0;
continue;
}
name[j++]=path[i];
}
name[j]='\0';
}
void display_attribute(struct stat buf,char *name) //获取文件属性并打印(-l)
{
char buf_time[32];
char name_attribute[11];
for(int i=0;i<10;i++)
{
name_attribute[i]='-';
}
name_attribute[10]='\0';
struct passwd *psd; //存放用户名
struct group *grp; //存放组名
//获取文件类型
if(S_ISLNK(buf.st_mode))
name_attribute[0]='l';
else if(S_ISREG(buf.st_mode))
name_attribute[0]='-';
else if(S_ISDIR(buf.st_mode))
name_attribute[0]='d';
else if(S_ISCHR(buf.st_mode))
name_attribute[0]='c';
else if(S_ISBLK(buf.st_mode))
name_attribute[0]='b';
else if(S_ISFIFO(buf.st_mode))
name_attribute[0]='f';
else if(S_ISSOCK(buf.st_mode))
name_attribute[0]='s';
//获取用户权限
if(buf.st_mode & S_IRUSR)
name_attribute[1]='r';
if(buf.st_mode & S_IWUSR)
name_attribute[2]='w';
if(buf.st_mode & S_IXUSR)
name_attribute[3]='x';
//获取用户组权限
if(buf.st_mode & S_IRGRP)
name_attribute[4]='r';
if(buf.st_mode & S_IWGRP)
name_attribute[5]='w';
if(buf.st_mode & S_IXGRP)
name_attribute[6]='x';
if(buf.st_mode & S_IROTH)
//获取其他权限
name_attribute[7]='r';
if(buf.st_mode & S_IWOTH)
name_attribute[8]='w';
if(buf.st_mode & S_IXOTH)
name_attribute[9]='x';
//打印
printf("%s ",name_attribute);
printf("%d ",(int)buf.st_nlink); //打印连接数
//得到用户名与用户组名
psd=getpwuid(buf.st_uid);
grp=getgrgid(buf.st_gid);
printf("%s %s ",psd->pw_name,grp->gr_name);
printf("%-d ",(int)buf.st_size); //打印文件大小
strcpy(buf_time,ctime(&buf.st_mtime));
buf_time[strlen(buf_time)-1]='\0'; //去掉换行符
printf(" %-s",buf_time); //打印文件时间信息
}
/*
*根据命令行参数和完整路径名显示目标文件
*参数flag:命令参数
*参数pathname;包含了文件名的路径名
*/
void display(int flag,char *pathname)
{
void display_R(int flag,char *path);
int i,j;
struct stat buf;
char name[260];
//用lstat 因为它牛,多个l,可以解析链接文件
if(lstat(pathname,&buf)==-1)
{
error("display",__LINE__);
}
take_out(pathname,name);
switch(flag)
{
case 1: //没有-a -l -R参数
if(name[0] !='.')
{
printf("%-6s ",name);
}
break;
case 3: //-a参数,显示包括隐藏文件在内的所有文件
{
printf("%-6s ",name);
}
break;
case 5: //-l参数
{
if(name[0]!='.')
{
display_attribute(buf,name);
printf(" %s\n",name);
}
break;
}
case 7: //同时有-a -l参数
{
display_attribute(buf,name);
printf(" %-s\n",name);
}
break;
case 9: //-R
{
display_R(flag,pathname);
break;
}
case 11: //-aR
{
printf(". ..\n");
display_R(flag,pathname);
break;
}
case 13: //-lR
{
display_attribute(buf,name);
printf(" ");
display_R(flag,pathname);
}
case 15: //-alR
{
display_attribute(buf,".");
printf(" .\n");
display_attribute(buf,"..");
printf(" ..\n");
display_R(flag,pathname);
break;
}
}
}
void display_R(int flag,char *path) //-R参数
{
struct stat buf;
struct stat buff;
DIR *dir;
struct dirent *ptr;
char allname[256][260],name[256][260],a[260],b[260];
int i,j,k,len,count;
if(lstat(path,&buf) == -1)
{
if(errno==13)
{
return ;
}
else
{
printf("error di: %s\n",path); //
//error("display_R",__LINE__);
return ;
}
}
if(S_ISDIR(buf.st_mode)) //为一个目录,还有文件或子目录
{
printf("\n%s\n",path); //打印目录名
count=0;
dir = opendir(path);
if(dir == NULL)
{
error("display_R",__LINE__);
}
i=0;
while((ptr = readdir(dir))!=NULL) //获取子目录下的文件 目录名,并连接成绝对路径名
{
len=0;
count++;
strncpy(allname[i],path,strlen(path));
allname[i][strlen(path)]='/';
allname[i][strlen(path)+1]='\0';
strncat(allname[i],ptr->d_name,strlen(ptr->d_name));
allname[i][strlen(allname[i])]='\0';
i++;
}
for(i=0;i<count;i++)
take_out(allname[i],name[i]);
for(i=0;i<count;i++)
{
if(name[i][0] != '.')
{
if(lstat(allname[i],&buff) == -1)
{
printf("error242");
}
if(S_ISDIR(buff.st_mode))
{
char *m=(char *)malloc(strlen(allname[i])*sizeof(char));
display_R(flag,m);
free(m);
}
else
{
if(flag > 11)
{
display_attribute(buff,allname[i]);
}
printf(" %s\n",name[i]);
}
}
else
{
printf("\n");
continue;
}
}
}
else
{
take_out(path,a);
if(a[0] != '.')
{
if(flag > 11)
{
display_attribute(buff,allname[i]);
}
printf(" %-s\n",a);
}
}
}
void display_dir(int flag_param,char *path)
{
void take_out(char *path,char *name); //
DIR *dir;
struct dirent *ptr;
int count=0;
char filename[256][260],fullname[256][260],name[256][260];
char temp[PATH_MAX];
//获取该目录下文件总数
dir=opendir(path);
if(dir==NULL)
{
error("oprndir",__LINE__);
}
int i = 0,j,k,len;
while((ptr = readdir(dir))!=NULL)
{
len=0;
count++;
// memset(filename[i], 0, strlen(filename[i]));
//memcpy(filename[i], ptr->d_name,sizeof(ptr->d_name));
strcpy(filename[i],ptr->d_name);
len = strlen(ptr->d_name);
filename[i][len]='\0';
i++;
}
closedir(dir);
if(count>256)
{
error("opendir",__LINE__);
}
//按字典序排序
for(i = 0;i<count-1;i++)
for(j = 0;j<count-1;j++)
{
if(strcmp(filename[j],filename[j+1])>0)
{
strcpy(temp,filename[j]);
strcpy(filename[j],filename[j+1]);
strcpy(filename[j+1],temp);
}
}
for(i=0;i<count;i++)
{
strncat(fullname[i],path,strlen(path));
fullname[i][strlen(path)]='/';
fullname[i][strlen(path)+1]='\0';
strncat(fullname[i],filename[i],strlen(filename[i]));
fullname[i][strlen(fullname[i])]='\0';
}
for(i=0;i<count;i++)
{
take_out(fullname[i],name[i]);
}
for(i=0;i<count;i++)
{
if(flag_param == 9 || flag_param == 11 || flag_param == 15 || flag_param == 13)
{
int flag=1;
if(name[i][0] == '.')
{
flag=0;
}
if(flag == 1)
{
display(flag_param,fullname[i]);
}
}
else
display(flag_param,fullname[i]);
printf("\n");
}
}
int main(int argc,char **argv)
{
struct stat buf;
int i,j,k;
char path[260]; //保存路径名
char param[32]; //保存命令行参数,目标文件名和目录名不在此数组
int flag_param=1; //参数种类,即是否有-l -a -R选项
//解析命令行参数,分析-l -a -al -la(真正ls 参数顺序无影响,只是我们要让程序知道)
j=0;
for(i=1;i<argc;i++)
{
if(argv[i][0]=='-')
{
for(k=1;k<strlen(argv[i]);k++,j++)
{
param[j]=argv[i][k]; //获取-后面的参数保存到数组param中
}
}
}
//只支持参数a l R如果有其他选项就报错
for(i=0;i<j;i++)
{
if(param[i] == 'a')
flag_param+=2;
else if(param[i] == 'l')
{
flag_param+=4;
}
else if(param[i] =='R')
{
flag_param+=8;
}
if(param[i] != 'a' && param[i] != 'l' && param[i] != 'R') //补-R
{
printf("我的ls无该功能");
exit(1);
}
}
param[j]='\0';
if(argc==1)
{
strcpy(path, ".");
display_dir(flag_param,path);
return 0;
}
else if(argc==2)
{
if(flag_param==1)
{
strcpy(path,argv[1]);
}
else
{
strcpy(path,".");
}
}
else if(argc==3)
{
strcpy(path,argv[2]);
}
//如果目标文件或目录不存在,报错并退出程序
if(stat(path,&buf)==-1)
{
error("it does not exist",__LINE__);
}
if(S_ISDIR(buf.st_mode)) //是一个目录
{
display_dir(flag_param,path);
}
else //是一个文件
{
display(flag_param,path);
}
return 0;
}
代码写的不好,有的地方很繁琐
ls -i的实现只要每次输出buf.st_ino
而ls -r,因为我这个就是按字典序输出,-r只要到过来就好了
ls -t的话可以用display_attribute函数中得到时间,做一个排序
上面这些是一些其他功能的思路
改BUG过程中知道了C语言的隐式声明这个万恶的存在,简单去了解了一下,就是说如果你在调用一个函数中没有先声明,会自动认为是int型的,如果接下来编译阶段你的函数实际上不是int返回值的,那么就会报错
还有一个是使用printf要记住加\n… Linux下标准输入输出都是带有缓存的,一般是行缓存。
对于标准输出,需要输出的数据并不是直接输出到终端上,而是首先缓存到某个地方,当遇到行刷新标志或者该缓存已满的情况下,才会把缓存的数据显示到终端设备上。
ANSI C中定义换行符’\n’可以认为是行刷新标志。所以,printf函数没有带’\n’是不会自动刷新输出流,直至缓存被填满。
在打印文件链接数和文件大小时遇到了一点小问题,无法正确输出,我将buf.st_size和buf.st_mtime强制类型转换为int就可以了,那原来他们应该按什么输出呢,求大佬指点
(写了好几天,都是玄学bug,strcpy函数炸了,过一段时间又自己好了,洗澡前改好的bug,测试一切正常,待机洗完澡回来测试同样的数据就又错了…诸如此类
原来心态很重要不是说说而已的,心态越差效率越低)