Linux编程:打造自己的ls命令

参考书本:《Linux C 编程实战》

首先说明,这些代码,是我在学习之后共享出来的,并没有完全的自创,但是对于学习 Linux 编程非常又帮助。

1、简单介绍

Linux 中, ls 命令将每个由 Directory 参数指定的目录或者每个由 File 参数指定的名称写到标准输出,说得通俗点,就是显示目录下的目录与文件信息。常用的参数又 -a 与 -l 参数。-a 参数表示显示隐藏文件,-l 参数表示显示文件的详细属性。

实现 ls 命令,我们必须知道这个函数的流程。

开始
|
解析输入参数
|
输入参数中是否有目录或文件 —是
|否 |
打印当前目录的文件信息 打印参数中每个目录或文件的信息
| |
结束 结束

整体流程,从上到下,会变为两条。

2、关键函数

(1)void display_attribute(struct stat buf,char* name)

该函数用于打印文件名 name 的文件信息

(2)void display_single(char* name)

该函数用于输出文件的文件名,若命令中没有 -l 参数,则输出文件名时要保证上下对其

(3)、void display(int flag,char* pathname)

该函数根据命令行参数(存放在 flag 中)和完整路径名(存放在 pathname 中)显示目标文件

(4)void displat_dir(int flag_param,char* path)

该函数为显示某个目录下的文件做准备,参数 flag_param 用于在调用 display 函数时作为其参数 flag 的实参,path 是要显示的目录

3、函数流程

  1. 获取该目录下文件的总数和最长的文件名
  2. 获取该目录下所有文件的文件名,存放与变亮 filenames 中
  3. 使用冒泡法对文件名按字母顺序排序,排序后文件名按字母顺序存储于 filenames 中
  4. 调用 display(int flag,char* pathname) 函数显示每个文件的信息。

4、函数代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <linux/limits.h>
#include <dirent.h>
#include <grp.h>
#include <pwd.h>
#include <errno.h>

#define		PARAM_NONE	0	// 无参数
#define		PARAM_A		1	// -a: 显示所有文件
#define		PARAM_L		2	// -l:一行显示一个文件的详细信息
#define		MAXROWLEN	80	// 一行显示的最多字符数

int		g_leave_len = MAXROWLEN;	// 一行剩余长度,用于输出对齐
int		g_maxlen;			// 存放某目录下最长文件名的长度

/* 错误处理函数,打印出错误所在行的行数和错误信息 */
void my_err(const char *err_string, int line)
{
	fprintf(stderr, "line:%d  ", line);
	perror(err_string);
	exit(1);
}

/* 获取文件属性并打印 */
void display_attribute(struct stat buf, char * name)
{
	char		buf_time[32];
	struct passwd	*psd;	//从该结构体中获取文件所有者的用户名
	struct group	*grp;	//从该结构体中获取文件所有者所属组的组名

	/*获取并打印文件类型*/
	if (S_ISLNK(buf.st_mode)) {
		printf("l");
	} else if (S_ISREG(buf.st_mode)) {
		printf("-");
	} else if (S_ISDIR(buf.st_mode)) {

		printf("d");
	} else if (S_ISCHR(buf.st_mode)) {
		printf("c");
	} else if (S_ISBLK(buf.st_mode)) {
		printf("b");
	} else if (S_ISFIFO(buf.st_mode)) {
		printf("f");
	} else if (S_ISSOCK(buf.st_mode)) {
		printf("s");
	}

	/*获取并打印文件所有者的权限*/
	if (buf.st_mode & S_IRUSR){
		printf("r");
	} else {
		printf("-");
	}
	if (buf.st_mode & S_IWUSR){
		printf("w");
	} else {
		printf("-");
	}
	if (buf.st_mode & S_IXUSR){
		printf("x");
	} else {
		printf("-");
	}

	/*获取并打印与文件所有者同组的用户对该文件的操作权限*/
	if (buf.st_mode & S_IRGRP){
		printf("r");
	} else {
		printf("-");
	}
	if (buf.st_mode & S_IWGRP){
		printf("w");
	} else {
		printf("-");
	}
	if (buf.st_mode & S_IXGRP){
		printf("x");
	} else {
		printf("-");
	}

	/*获取并打印其它用户的对该文件的操作权限*/
	if (buf.st_mode & S_IROTH){
		printf("r");
	} else {
		printf("-");
	}
	if (buf.st_mode & S_IWOTH){
		printf("w");
	} else {
		printf("-");
	}
	if (buf.st_mode & S_IXOTH){
		printf("x");
	} else {
		printf("-");
	}
	printf("    ");

	/*根据uid与gid获取文件所有者的用户名与组名*/
	psd = getpwuid(buf.st_uid);
	grp = getgrgid(buf.st_gid);
	printf("%4d ",buf.st_nlink);   // 打印文件的链接数
	printf("%-8s", psd->pw_name);
	printf("%-8s", grp->gr_name);
	printf("%6d",buf.st_size);     // 打印文件的大小
	strcpy(buf_time, ctime(&buf.st_mtime));
	buf_time[strlen(buf_time) - 1] = '';	// 去掉换行符
	printf("  %s", buf_time);				// 打印文件的时间信息
}

/* 在没有使用-l选项时,打印一个文件名,打印时上下行之间进行对齐 */
void display_single(char *name)
{
	int i, len;

	// 如果本行不足以打印一个文件名则换行
	if (g_leave_len < g_maxlen) {
		printf("n");
		g_leave_len = MAXROWLEN;
	}
	len = strlen(name);
	len = g_maxlen - len;
	printf("%-s", name);
	for (i=0; i<len; i++) {
		printf(" ");
	}
	printf("  ");
	//下面的2指示空两格
	g_leave_len -= (g_maxlen + 2);
}

/*
*	根据命令行参数和完整路径名显示目标文件
*	参数flag: 命令行参数
*	参数pathname: 包含了文件名的路径名
*/
void display(int flag, char * pathname)
{
	int		i, j;
	struct stat	buf;
	char		name[NAME_MAX + 1];
	/*从路径中解析出文件名*/
	for (i=0, j=0; i<strlen(pathname); i++) {
		if (pathname[i] == '/') {
			j = 0;
			continue;
		}
		name[j++] = pathname[i];
	}
	name[j] = '';

	/*用lstat而不是stat以方便解析链接文件*/
	if ( lstat(pathname, &buf) == -1 ) {
		my_err("stat", __LINE__);
	}
	switch (flag) {
		case PARAM_NONE:  // 没有-l和-a选项
			if (name[0] != '.') {
				display_single(name);
			}
			break;
		case PARAM_A:	  // -a:显示包括隐藏文件在内的所有文件
			display_single(name);
			break;
		case PARAM_L:	  // -l:每个文件单独占一行,显示文件的详细属性信息
			if (name[0] != '.') {
				display_attribute(buf, name);
				printf("  %-sn", name);
			}
			break;
		case PARAM_A + PARAM_L:		// 同时有-a和-l选项的情况
			display_attribute(buf, name);
			printf("  %-sn", name);
			break;
		default:
			break;
	}
}

void display_dir(int flag_param, char *path)
{
        DIR            *dir;
        struct dirent  *ptr;
        int            count = 0;
        char           filenames[256][PATH_MAX+1],temp[PATH_MAX+1];
		// 获取该目录下文件总数和最长的文件名
        dir = opendir(path);
        if (dir == NULL) {
                my_err("opendir", __LINE__);
        }
        while ((ptr = readdir(dir))!=NULL) {
                if (g_maxlen < strlen(ptr->d_name))
                         g_maxlen =  strlen(ptr->d_name);
                count++;
        }
        closedir(dir);
        if(count>256)
                my_err("too many files under this dir",__LINE__);
        int i, j, len = strlen(path);
		// 获取该目录下所有的文件名
        dir = opendir(path);
        for(i = 0; i < count; i++){
                ptr = readdir(dir);
                if( ptr == NULL){
                        my_err("readdir",__LINE__);
                }
                strncpy(filenames[i],path,len);
                filenames[i][len] = '';
                strcat(filenames[i],ptr->d_name);
                filenames[i][len+strlen(ptr->d_name)] = '';
        }
		// 使用冒泡法对文件名进行排序,排序后文件名按字母顺序存储于filenames
        for(i = 0; i < count-1; i++)
                for(j = 0; j < count-1-i; j++) {
                        if( strcmp(filenames[j],filenames[j+1]) > 0 ) {
                                strcpy(temp,filenames[j+1]);
                                temp[strlen(filenames[j+1])] = '';
                                strcpy(filenames[j+1],filenames[j]);
                                filenames[j+1][strlen(filenames[j])] = '';
                                strcpy(filenames[j], temp);
                                filenames[j][strlen(temp)] = '';
                        }
                }
        for(i = 0; i < count; i++)
                display(flag_param, filenames[i]);
        closedir(dir);
        // 如果命令行中没有-l选项,打印一个换行符
        if( (flag_param & PARAM_L) == 0)
                printf("n");}
int main(int argc, char ** argv)
{
	int		i, j, k, num;
	char		path[PATH_MAX+1];
	char		param[32];	      // 保存命令行参数,目标文件名和目录名不在此列
	int		flag_param = PARAM_NONE; // 用来标志参数种类,即是否有-l和-a选项
	struct stat	buf;
	/*命令行参数的解析,分析-l、-a、-al、-la选项*/
	j   = 0,
	num = 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中
			}
		num++;  // 保存"-"的个数
		}
	}
	/*只支持参数a和l,如果含有其他选项就报错*/
	for(i=0; i<j; i++) {
		if (param[i] == 'a') {
			flag_param |= PARAM_A;
			continue;
		} else if (param[i] == 'l') {
			flag_param |= PARAM_L;
			continue;
		} else {
			printf("my_ls: invalid option -%cn", param[i]);
			exit(1);
		}
	}
	param[j] = '';
	// 如果没有输入文件名或目录,就显示当前目录
	if ((num + 1) == argc) {
		strcpy(path, "./");
		path[2] = '';
		display_dir(flag_param, path);
		return 0;
	}
	i=1;
	do {
		// 如果不是目标文件名或目录,解析下一个命令行参数
		if (argv[i][0] == '-') {
			i++;
			continue;
		} else {
			strcpy(path, argv[i]);
			// 如果目标文件或目录不存在,报错并退出程序
			if ( stat(path, &buf) == -1 )
				my_err("stat", __LINE__);
			if ( S_ISDIR(buf.st_mode) ) {   // argv[i]是一个目录
				// 如果目录的最后一个字符不是'/',就加上'/'
				if ( path[ strlen(argv[i])-1 ] != '/') {
					path[ strlen(argv[i]) ] = '/';
					path[ strlen(argv[i])+1 ] = '';
				}
				else
					path[ strlen(argv[i]) ] = '';
				display_dir(flag_param,path);
				i++;
			}
			else {  //argv[i]是一个文件
				display(flag_param, path);
				i++;
			}
		}
	} while (i<argc);
	return 0;
}

文章有点长,主要是代码,因为有一些关键代码,所以都添上来了,如果能看到这里,非常的感谢你的支持。

 

{ 发表评论? }

  1. 网页快照

    不错的文章。。顶下

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>