您的位置:首页 > 运维架构 > Linux

Linux下的 ls 命令的简单实现

2014-12-28 23:06 621 查看

一、实现要求

又到了期末的时候,Linux实践课的老师挺厚道,布置了算是一个入门的大作业:实现一个简单版本的ls命令。根据Linux技术与实践课程学习的基础知识,编写一个可以在Unix/Linux环境下运行的程序:ls2.c,功能类似于系统自带的ls命令。可以显示特定目录下的所有文件的属性信息,或者直接列出所有文件。

感觉自己写的代码有点乱,但是功能都实现了,这点还不错。目录下的文件多了会发生段错误,不懂为啥,C语言就是这么那啥。再说吧~

具体要求如下:

不加 –l 参数,如“ls2 /home”,可以直接列出目录下所有的文件,并按照升序显示。显示在终端里有格式的缩进,即分栏处理。
加 –l 参数时,如“ls2 –l /home ”,会详细显示所有的文件信息,与 ls –l 的系统命令基本一致。每行显示一个文件或者文件夹的信息,属性依次是:文件类型,当前用户权限,当前用户组权限,其他用户权限,用户名,组名,链接数,文件大小(字节),修改的最后时间。
注意格式化输出,对齐。
所有的隐藏文件都不显示。

二、背景讲解

先要讲两个头文件:<dirent.h> 和 <sys/stat.h> ,都是 POSIX.1标准定义的unix类的头文件。

1. <dirent.h>

第一个,dirent.h,是用来处理目录,包含了许多UNIX系统服务的函数原型,例如opendir函数、readdir函数.这里我们可以用来通过文件名打开,关闭,读取特定的文件夹信息,或者说,该目录的信息。可以参考ls_header.h的34开始的代码。
/*
opendir函数可以按照文件名打开一个文件,
并返回一个DIR型的指针,若是返回NULL则说明文件打开失败。
*/
DIR *opendir(const char *pathname);

/*
根据上面的DIR指针,我们可以读取这个目录,readdir会返回一个dirent的结构体指针。
如果不为NULL,说明下面还有文件,可以继续调用readdir来获取下一个文件。
我们可以用得到的dirent结构体指针,获取文件的一些信息。
*/
struct dirent *readdir(DIR *dp);

/* 关闭文件目录 */
int closedir(DIR *dirp);
dirent结构体组成如下,不过这里只用到了d_name,其他的暂时没啥用。
struct dirent
{
long 			d_ino;			/* inode number 索引节点号 */
off_t 			d_off;			/* offset to this dirent 在目录文件中的偏移 */
unsigned short 	d_reclen; 			/* length of this d_name 文件名长 */
unsigned char 	d_type; 			/* the type of d_name 文件类型 */
char 			d_name [NAME_MAX+1]; 	/* file name 文件名,最长256字符 */
}


2. <sys/stat.h>
这个头文件里面提供一些有用的东西,能根据全路径的文件名获得一个文件(文件夹在Linux中也算是一个特殊的文件,也有各种读写执行的权限)。里面有用的是stat函数,可以传进去一个pathname,结果会保存在一个stat结构体,即buf中,如下:

int stat(const char *restrict pathname,struct stat *restrict buf);stat结构体可以得到很多关于这个文件的属性信息,也就是我们这里要用 ls 命令列举出来的信息。当然啦,还要转换一下才有可读性。ls_util里面的函数主要就是用来转换成我们要的字符串的。
结构如下:
struct stat{
mode_t st_mode; //文件类型和权限信息
ino_t st_ino; //i结点标识
dev_t st_dev; //device number (file system)
dev_t st_rdev; //device number for special files
nlink_t st_nlink; //符号链接数
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
off_t st_size; //size in bytes,for regular files
time_t st_st_atime; //最后一次访问的时间
time_t st_mtime; //文件内容最后一次被更改的时间
time_t st_ctime; //文件结构最后一次被更改的时间
blksize_t st_blksize; //best I/O block size
blkcnt_t st_blocks; //number of disk blocks allocated
};当然,我们这里只用到了这几个:
st_mode 文件类型和许可权限
st_nlink 文件链接数
st_uid 文件属主id
st_gid 文件属主所在组的id
st_size 文件的字节数
st_mtime 文件最后修改的时间

三、程序流程

程序实现有三个文件,ls2.c、ls_header.h、ls_util.h文章的最后有附上。这里一个一个讲。

1. ls2.c中main函数的逻辑

首先,我们在运行程序的时候,会从命令中读取参数,保存在 argv 的二维数组中,如“./ls2 –l  /home/halfish”,所以第三个参数就是我们要处理的目录名。

在ls2.c中,主要处理这些参数,对应该去调用带参数(lsLong函数)或者是不带参数(lsShort)的函数。

注意这里用户可能没有输入目录,所以默认要是“.”,即当前目录,作为参数。

其次,还要区分,是显示目录还是显示文件的信息。

2.  ls_header.h的函数

lsLong对读取的参数作进一步处理,在ls_long_file中得到单个文件的详细信息,并保存在自定义的结构体attribute_file中。然后做一次选择排序,并格式化输出。

格式化输出时,要考虑每个属性值的缩进,所以要先遍历一次所有要输出的文件属性,得到最大的长度,并按照这个长度缩进才能让输出显得整齐。

lsShort逻辑简单一点,所有要显示的文件名存储在filenames中。稍微麻烦一点的是,要根据当前终端的大小分栏显示。可能要用到ls_util.h中的getTerminatorSize函数来读取终端的列数和行数。

3.  ls_util.h头文件

定义很多函数,把dirent结构体中的信息转换成我们最后要显示的字符串。见代码中的注释吧~

四、附上代码

ls2.c

#include <stdio.h>
#include "ls_header.h"

int main(int argc, char **argv) {
// 处理参数, lsLong是带有 -l 参数的, lsShort为没有-l参数的ls命令
int i;
if (argc == 1) {						/* ls . */
lsShort("./");
return 0;
}
if (argc == 2 && !strcmp(argv[1], "-l") ) {			/* ls -l . */
lsLong("./");
return 0;
}
if (argc > 2 && !strcmp(argv[1], "-l") ) {			/* ls -l dirname */
for(i = 2; i < argc; ++ i) {
printf("%s:\n", argv[i]);
lsLong(argv[i]);
if(i != argc - 1)
printf("\n");
}
return 0;
} else {							/* ls dirname */
for (i = 1; i < argc; ++ i) {
printf("%s:\n", argv[i]);
lsShort(argv[i]);
if(i != argc - 1)
printf("\n");
}
return 0;
}

return 0;
}


ls_header.h

#include <stdio.h>
#include <dirent.h>	// for DIR, dirent
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "ls_util.h"

// struct
struct attribute file_attribute[200]; // maximum 200

void lsLong(char *dirname)
{

DIR *mydir = opendir( dirname );			/* directory */

char filename[20];
int file_num = 0;

if (mydir == NULL) {
// 显示单个文件的详细信息
strcpy(filename, dirname);
ls_long_file("./", filename, &file_attribute[0]);
++ file_num;
} else {
// 考虑用户输入文件夹没有输入反斜杠的情况
int len = strlen(dirname);
if (dirname[len - 1] != '/')
{
dirname[len] = '/';
dirname[len+1] = '\0';
}

// 循环得到当前目录下的所有文件名,并存储在自定义的结构体中
struct dirent *mydirent;			/* file */
while ( (mydirent = readdir( mydir )) != NULL) {
char filename[20];
strcpy(filename, mydirent->d_name);
// 不能为隐藏文件
if (!strcmp(filename, ".") || !strcmp(filename, ".") || filename[0] != '.') {
ls_long_file(dirname, filename, &file_attribute[file_num ++]);
}

}
closedir( mydir );
}

// 按照文件名排序
struct attribute temp;
char filename1[20];
char filename2[20];
int i, j;

for (i = 0; i < file_num; ++i)
{
for (j = i + 1; j < file_num; ++ j)
{
strcpy(filename1, file_attribute[i].filename);
strcpy(filename2, file_attribute[j].filename);
if ( strcmp(filename1, filename2) > 0)
{
temp = file_attribute[i];
file_attribute[i] = file_attribute[j];
file_attribute[j] = temp;
}
}
}

// 格式化输出时,考虑每个属性值的范围
int max_mode = 0;
int max_links = 0;
int max_user_name = 0;
int max_group_name = 0;
int max_size = 0;
int max_mtime = 0;
int max_filename = 0;
int max_extra = 0;

for (i = 0; i < file_num; ++ i)
{
if ( max_mode < strlen(file_attribute[i].mode) ) {
max_mode = strlen(file_attribute[i].mode);
}

if (max_links < f(file_attribute[i].links)) {
max_links = f(file_attribute[i].links);
}

if ( max_user_name < strlen(file_attribute[i].user_name) ) {
max_user_name = strlen(file_attribute[i].user_name);
}

if ( max_group_name < strlen(file_attribute[i].group_name) ) {
max_group_name = strlen(file_attribute[i].group_name);
}

if (max_size < f(file_attribute[i].size)) {
max_size = f(file_attribute[i].size);
}

if ( max_mtime < strlen(file_attribute[i].mtime) ) {
max_mtime = strlen(file_attribute[i].mtime);
}

if ( max_filename < strlen(file_attribute[i].filename) ) {
max_filename = strlen(file_attribute[i].filename);
}

if ( max_extra < strlen(file_attribute[i].extra) ) {
max_extra = strlen(file_attribute[i].extra);
}
}

for (i = 0; i < file_num; ++i)
{
char format[50];
// 定义输出的格式
sprintf(format, "%%%ds %%%dd %%%ds %%%ds %%%dld %%%ds %%s%%s\n",
max_mode, max_links, max_user_name, max_group_name, max_size,
max_mtime);
// 按照定义的输出格式输出
printf(format, file_attribute[i].mode, file_attribute[i].links,
file_attribute[i].user_name, file_attribute[i].group_name, file_attribute[i].size,
file_attribute[i].mtime, file_attribute[i].filename, file_attribute[i].extra);
}

}

// 处理不带 -l 参数的 ls 命令
void lsShort(char *dirname) {

DIR *mydir = opendir( dirname );			/* directory */

// 用来暂时存储要显示的目录下的所有文件名,可以看到最大可以支持200个文件,但是每个文件名最长为20
char filenames[200][20];
int file_num = 0;

if (mydir == NULL) {
// 直接显示该文件
printf("%s\n\n", dirname);
return ;
} else {
// 循环检查下面有多少文件,并把文件名全部放到filenames数组里
struct dirent *mydirent;			/* file */
while ( (mydirent = readdir( mydir )) != NULL) {
char fname[20];
strcpy(fname, mydirent->d_name);
if (fname[0] != '.' ) {
strcpy(filenames[file_num], mydirent->d_name);
file_num ++;
}
}
closedir( mydir );
}

// 文件名排序
int i, j;
char temp[20];
for(i = 0; i < file_num; ++ i) {
for(j = i+1; j < file_num; ++ j) {
if(strcmp(filenames[i], filenames[j]) > 0) {
strcpy(temp, filenames[i]);
strcpy(filenames[i], filenames[j]);
strcpy(filenames[j], temp);
}
}
}

// 确定所有文件里面最长的文件名的长度
int max_len = 0;

for(i = 0; i < file_num; ++ i) {
int len = strlen(filenames[i]);
if(len > max_len) {
max_len = len;
}
}

// 得到当前终端的分辨率
int cols = 80;
int lines = 24;
getTerminatorSize(&cols, &lines);

char format[20];
sprintf(format, "%%-%ds  ", max_len);

// 格式化输出,当长度大于终端的列数时,换行
int current_len = 0;
for(i = 0; i < file_num; ++ i) {
printf(format, filenames[i]);
current_len += max_len + 2;
if(current_len + max_len + 2 > cols) {
printf("\n");
current_len = 0;
}
}
printf("\n");
}


ls_util.h

#include <stdio.h>
#include <sys/stat.h>	// 这个头文件用来得到文件的详细信息
#include <string.h>
#include <time.h>	// 时间头文件
#include <pwd.h>	// 用来得到用户名
#include <grp.h>	// 用来得到组名

// 结构体,用来存储要输出的每个属性值
struct attribute
{
char mode[10];		// 文件属性和权限
int links;		// 链接数
char user_name[20];	// 用户名
char group_name[20];	// 所在的用户组
long size;		// 文件大小
char mtime[20];		// 最后修改的时间
char filename[255];	// 文件名
char extra[3];		// 用来显示时候要加 "*"(可以执行的文件) 或者 "/" (目录) 的额外字符串
};

// 计算整数 n 有几位
int f(long n) {
int ret = 0;
while(n) {
n = n / 10;
++ ret;
}
return ret;
}

// 得到终端的列数和行数
void getTerminatorSize(int *cols, int *lines) {

#ifdef TIOCGSIZE
struct ttysize ts;
ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
*cols = ts.ts_cols;
*lines = ts.ts_lines;
#elif defined(TIOCGWINSZ)
struct winsize ts;
ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
*cols = ts.ws_col;
*lines = ts.ws_row;
#endif /* TIOCGSIZE */

}

// 由 int 型的 mode,得到实际要显示的字符串
void mode2str(int mode, char str[])
{
strcpy(str, "----------\0");
if(S_ISDIR(mode)) str[0] = 'd';
if(S_ISCHR(mode)) str[0] = 'c';
if(S_ISBLK(mode)) str[0] = 'b';
if(S_ISLNK(mode)) str[0] = 'l';

if(mode & S_IRUSR) str[1] = 'r';
if(mode & S_IWUSR) str[2] = 'w';
if(mode & S_IXUSR) str[3] = 'x';

if(mode & S_IRGRP) str[4] = 'r';
if(mode & S_IWGRP) str[5] = 'w';
if(mode & S_IXGRP) str[6] = 'x';

if(mode & S_IROTH) str[7] = 'r';
if(mode & S_IWOTH) str[8] = 'w';
if(mode & S_IXOTH) str[9] = 'x';
}

// 根据用户的 id 值,得到用户名
void uid2str(uid_t uid, char *user_name) /* 将uid转化成username */
{
struct passwd *pw_ptr;
pw_ptr = getpwuid(uid);

if( pw_ptr == NULL) {
sprintf(user_name, "%d", uid);
} else {
strcpy(user_name, pw_ptr->pw_name);
}
}

// 根据用户组的 id 值,得到用户组名
void gid2str(gid_t gid, char *group_name) /* 将uid转化成username */
{
struct group *grp_ptr;
grp_ptr = getgrgid(gid);

if( grp_ptr == NULL) {
sprintf(group_name, "%d", gid);
} else {
strcpy(group_name, grp_ptr->gr_name);
}
}

// 时间的格式化字符串, 注意这里我把前面的星期和后面的年份都去掉了
void time2str(time_t t, char *time_str)
{
strcpy( time_str, ctime(&t) + 4);
time_str[12] = '\0';
}

// 要显示的某一个文件详细信息,并把信息放在结构体 attribute 中
void ls_long_file(char *dirname, char *filename, struct attribute *file_attri)
{
// 根据文件夹名和文件名得到全名
char fullname[256];
strcpy(fullname, dirname);
strcpy(fullname + strlen(dirname), filename);

struct stat mystat;
if ( stat(fullname, &mystat) == -1) {
printf("ls_long_file: stat error\n");
} else {
// 这里参考 <stat.h> 头文件
int mode   = (int)  mystat.st_mode;
int links  = (int)  mystat.st_nlink;
int uid    = (int)  mystat.st_uid;
int gid    = (int)  mystat.st_gid;
long size  = (long) mystat.st_size;
long mtime = (long) mystat.st_mtime;

char str_mode[10];			/* 文件类型和许可权限, "drwxrwx---" */
char str_user_name[20];
char str_group_name[20];
char str_mtime[20];

// 这里就是直接调用上面的函数啦,洒家就不解释了
mode2str(mode, str_mode);
uid2str(uid, str_user_name);
gid2str(gid, str_group_name);
time2str(mtime, str_mtime);

char extra[3] = "\0\0";
if (str_mode[0] == 'd') {
extra[0] = '/';
} else if (str_mode[0] == '-' && str_mode[3] == 'x') {
extra[0] = '*';
}

// 存储在结构体中
strcpy(file_attri->mode, str_mode);
file_attri->links = links;
strcpy(file_attri->user_name, str_user_name);
strcpy(file_attri->group_name, str_group_name);
file_attri->size = size;
strcpy(file_attri->mtime, str_mtime);
strcpy(file_attri->filename, filename);
strcpy(file_attri->extra, extra);

// 大概要得到的效果就是下面的情况啦!
/*
drwxr-xr-x  5 halfish halfish  4096 12月 28 10:35 Downloads/
*/
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: