Linux下 C语言实现 ls命令以及ls -l命令

  • Post author:
  • Post category:linux




前期准备



DIR结构体

首先开始先来看看DIR这个结构体 , 以下为DIR结构体的定义 :

  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,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息(摘自《UNIX环境高级编程(第二版)》).

函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用:

  struct dirent *readdir(DIR *dp);   
      
  void rewinddir(DIR *dp);   
      
  int closedir(DIR *dp);   
      
  long telldir(DIR *dp);   
      
  void seekdir(DIR *dp,long loc);  

关于DIR的结构 , 我们了解这么对就可以了.



dirent结构体

接着在来看看dirent结构体 , 首先我们要弄清楚目录文件(directory file)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针(摘自《UNIX环境高级编程(第二版)》) .从定义能够看出,dirent不仅仅指向目录,还指向目录中的具体文件,readdir函数同样也读取目录下的文件,这就是证据。以下为dirent结构体的定义:

  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 (null-terminated) 文件名,最长255字符 */  
   }  

从上述定义也能够看出来,dirent结构体存储的关于文件的信息很少,所以dirent同样也是起着一个索引的作用,如果想获得类似ls -l那种效果的文件信息,必须要靠stat函数了 .



stat结构体

通过readdir函数读取到的文件名存储在结构体dirent的d_name成员中,而函数

int stat(const char *file_name, struct stat *buf);

的作用就是获取文件名为d_name的文件的详细信息,存储在stat结构体中。以下为stat结构体的定义:

 struct stat 
  {   
     mode_t     st_mode;       //文件访问权限   
      
     ino_t      st_ino;       //索引节点号   
      
     dev_t      st_dev;        //文件使用的设备号   
      
     dev_t      st_rdev;       //设备文件的设备号   
      
     nlink_t    st_nlink;      //文件的硬连接数   
      
     uid_t      st_uid;        //所有者用户识别号   
      
     gid_t      st_gid;        //组识别号   
      
     off_t      st_size;       //以字节为单位的文件容量   
      
     time_t     st_atime;      //最后一次访问该文件的时间   
      
     time_t     st_mtime;      //最后一次修改该文件的时间   
      
     time_t     st_ctime;      //最后一次改变该文件状态的时间   
      
     blksize_t st_blksize;    //包含该文件的磁盘块的大小   
      
     blkcnt_t   st_blocks;     //该文件所占的磁盘块   
      
   };  

这样前期的准备工作就做的差不多了 , 让我们来看看ls怎么实现 .



st_mode结构体

st_mode这个变量用来判断文件类型

st_mode是用特征位来表示文件类型的,特征位的定义如下:

S_IFMT      0170000     文件类型的位遮罩
S_IFSOCK    0140000     socket
S_IFLNK     0120000     符号链接(symbolic link)
S_IFREG     0100000     一般文件
S_IFBLK     0060000     区块装置(block device)
S_IFDIR     0040000     目录
S_IFCHR     0020000     字符装置(character device)
S_IFIFO     0010000     先进先出(fifo)
S_ISUID     0004000     文件的(set user-id on execution)位
S_ISGID     0002000     文件的(set group-id on execution)位
S_ISVTX     0001000     文件的sticky位
S_IRWXU     00700       文件所有者的遮罩值(即所有权限值)
S_IRUSR     00400       文件所有者具可读取权限
S_IWUSR     00200       文件所有者具可写入权限
S_IXUSR     00100       文件所有者具可执行权限
S_IRWXG     00070       用户组的遮罩值(即所有权限值)
S_IRGRP     00040       用户组具可读取权限
S_IWGRP     00020       用户组具可写入权限
S_IXGRP     00010       用户组具可执行权限
S_IRWXO     00007       其他用户的遮罩值(即所有权限值)
S_IROTH     00004       其他用户具可读取权限
S_IWOTH     00002       其他用户具可写入权限
S_IXOTH     00001       其他用户具可执行权限
摘自《Linux C 函数库参考手册》



ls的实现



大致思路

大致的思路是(因为时间过的有点长 , 当时是有些具体的想法都忘记了 , 将就着看吧) :

编者要完成的是一个

经过排序的ls

.

首先根据DIR这个结构体 ,

打开目录

, 再根据dirent这个结构体 ,

读出目录中的文件名

, 并把它

保存在一个二维数组中

(编者这里将数组设置成了100*100 , 足够保存) , 然后

对于目录文件和可执行文件进行特殊标记

(这里就是在最后 , 标记一个数字 , 因为文件名肯定取不到) , 因为在Linux中不同的文件的颜色是不同的, 然后使用快排对文件名进行排序 , 然后在输出的时候对后最一个位置进行甄别 ,如果是可执行文件或者目录的话, 使其输出相应的颜色.

对于文件名的快排 , 附上链接同大家学习.


https://blog.csdn.net/f_zyj/article/details/51484751



以下是源代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<dirent.h>
#include<sys/stat.h>
#define SIZE_MAX 100

int cmp(const void *a,const void *b)
{
	return (strcmp((char*)a,(char*)b));
}

int main()
{
	char path[256] = {0};
	getcwd(path,256);
	DIR *pdir = opendir(path);
	if(pdir == NULL)
	{
		exit(1);
	}
	struct dirent *p = NULL;
	struct stat st;
	int count = 0;
	char s[SIZE_MAX][SIZE_MAX];
	while((p = readdir(pdir)) != NULL)
	{
		if(strncmp(p->d_name,".",1) == 0)
		{
			continue;
		}
		stat(p->d_name,&st);
		if(S_ISDIR(st.st_mode))
		{
			s[count][SIZE_MAX -1] = '9';
		}
		else
		{
			if(st.st_mode&(S_IXUSR | S_IXGRP | S_IXOTH))
			{
				s[count][SIZE_MAX -1] = '8';
			}
		}
		strcpy(s[count++],p->d_name);
	}
	qsort(s,count,sizeof(s[0]),cmp);
	int i = 0;
	for(i = 0;i < count;i++)
	{
		if(s[i][SIZE_MAX -1] == '9')
		{
			printf("\033[1;34m%s  \033[0m",s[i]);
		}
		else
		{
			if(s[i][SIZE_MAX -1] == '8')
			{
				printf("\033[1;32m%s  \033[0m",s[i]);
			}
			else
			{
				printf("%s  ",s[i]);
			}
		}
	}
	printf("\n");
	closedir(pdir);
	exit(0);
}



运行结果 :

在这里插入图片描述



ls -l 的实现



大致思路:

获取当前的工作目录 , 讲读取到的文件都写入 自己创建的二维数组当中 , 然后进行快排 , 之后再与DIR结构结构体中读取到的目录进行比对 , 根据 st_mode 来对文件进行判断 , 然后在进行输出(跟ls的实现基本相同).

那么来说说在编写的时候遇到的问题 :


  • 1.total值的计算

  • 2.文件时间的显示


1. total值的计算

由于total的单位是k , 4096是块的默认大小 . 所以需要做的是 , 将文件所有的文件大小加起来 , 然后计算出所占的块数(不满一块的按照一块来计算) , 然后根据 total = 块数 * 4 , 就能得出total的值.

注意 : ls -l 是不计算符号链接的 .



2. 文件时间的显示

在ls -l的命令中 , 会显示文件的创建时间 . 所以要使用 localtime() 用来获取系统时间(精度为秒)

, 这样的话就能正确输出时间.



tm结构体
#ifndef _TM_DEFINED
struct tm {
          int tm_sec;       /* 秒 – 取值区间为[0,59] */
          int tm_min;       /* 分 - 取值区间为[0,59] */
          int tm_hour;      /* 时 - 取值区间为[0,23] */
          int tm_mday;      /* 一个月中的日期 - 取值区间为[1,31] */
          int tm_mon;       /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
          int tm_year;      /* 年份,其值等于实际年份减去1900 */
          int tm_wday;      /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
          int tm_yday;      /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
          int tm_isdst;     /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
          };
#define _TM_DEFINED
#endif



以下是源代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<dirent.h>
#include<sys/stat.h>
#include<pwd.h>
#include<grp.h>
#include<time.h>
#define SIZE_MAX 100

int cmp(const void *a,const void *b)
{
	return (strcmp((char*)a,(char*)b));
}

int main()
{
	char path[256] = {0};
	getcwd(path,256);
	DIR *pdir = opendir(path);
	if(pdir == NULL)
	{
		exit(1);
	}
	struct dirent *p = NULL;
	struct stat st;
	int total = 0;
	long block=0;
	char s[SIZE_MAX][SIZE_MAX] = {0};
	int count = 0;
	while((p = readdir(pdir)) != NULL)//计算total
	{
		stat(p->d_name,&st);
		if(strncmp(p->d_name,".",1) == 0)
		{
			continue;
		}
		if(st.st_size <=  4096)
		{
			block++;
		}
		else
		{
			block += (st.st_size/4096) +1;
		}
		strcpy(s[count++],p->d_name);
	}
	qsort(s,count,sizeof(s[0]),cmp);
	closedir(pdir);
	
	total = block*4;
	printf("total %d\n",total);
	int i = 0;
	for(i = 0;i < count;i++)
	{
		pdir = opendir(path);
		while((p = readdir(pdir)) != NULL)
		{
			if(strncmp(p->d_name,".",1) == 0)
			{
				continue;
			}	
			if(strcmp(p->d_name,s[i]) != 0)
			{
				continue;
			}
			stat(p->d_name,&st);
			if(S_ISDIR(st.st_mode))//判断是否为目录
			{
				printf("d");
			}
			if((st.st_mode & S_IFREG) == S_IFREG)//是否为普通文件
			{
				printf("-");
			}
			if((st.st_mode & S_IFLNK) == S_IFLNK)//是否为链接文件
			{
				printf("l");
			}
			if((st.st_mode & S_IFCHR) == S_IFCHR)//是否为字符特殊文件
			{
				printf("c");
			}
			if((st.st_mode & S_IFBLK) == S_IFBLK)//是否为块特殊文件
			{
				printf("b");
			}
			if((st.st_mode & S_IFIFO) == S_IFIFO)//是否为管道或FIFO文件
			{
				printf("p");
			}
			if((st.st_mode & S_IFSOCK) == S_IFSOCK)//是否为套接字文件
			{
				printf("s");
			}
			if((st.st_mode & S_IRUSR) == S_IRUSR)
			{
				printf("r");	
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IWUSR) == S_IWUSR)
			{	
				printf("w");
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IXUSR) == S_IXUSR)
			{
				printf("x");
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IRGRP) == S_IRGRP)
			{
				printf("r");
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IWGRP) == S_IWGRP)
			{
				printf("w");
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IXGRP) == S_IXGRP)
			{
				printf("x");
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IROTH) == S_IROTH)
			{
				printf("r");
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IWOTH) == S_IWOTH)
			{
				printf("w");
			}
			else
			{
				printf("-");
			}
			if((st.st_mode & S_IXOTH) == S_IXOTH)
			{
				printf("x");
			}
			else
			{
				printf("-");
			}
			printf(". ");
			printf("%d ",st.st_nlink);//硬连接数

			struct passwd *pwd;
			pwd = getpwuid(st.st_uid);//所有者用户识别码
			printf("%-4s ",pwd->pw_name);

			struct group *grp;
			grp = getgrgid(st.st_gid);//组识别码
			printf("%-4s ",grp->gr_name);
			printf("%5d ",st.st_size);//以字节为单位的文件容量

			struct tm *t = localtime(&st.st_mtime);//转换为人类可识别的时间
			char *wday[12] = {"Jan","Feb","Mar","Apr","May","June","July","Aug","Sept","Oct","Nov","Dec"};
			printf("%s  %d ",wday[t->tm_mon],t->tm_mday);
			if(t->tm_hour < 10)
			{
				printf("0%d:",t->tm_hour);
			}
			else
			{
				printf("%d:",t->tm_hour);
			}
			if(t->tm_min < 10)
			{
				printf("0%d ",t->tm_min);
			}
			else
			{
				printf("%d ",t->tm_min);
			}

			if(S_ISDIR(st.st_mode))
			{
				printf("\033[1;34m%s \n\033[0m",p->d_name);
			}
			else
			{
				if(st.st_mode&(S_IXUSR | S_IXGRP | S_IXOTH))
				{
					printf("\033[1;32m%s \n\033[0m",p->d_name);
				}
				else
				{
					printf("%s \n",p->d_name);
				}
			}	
		}
		closedir(pdir);
	}
	exit(0);
}



运行结果 :

在这里插入图片描述


编者的这种方法仅仅是为了完成而完成 , 它的效率并不高 , 尤其是在判断文件种类的时候 .这代码仅供学习吧 , 如果有人有更好的方法 , 可以来交流.



版权声明:本文为Nonpc123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。