Bootstrap

在Linux下使用程序实现ls -l 的功能

需求分析

ls -l 的使用方式

        ls -l 的基本使用方式有两种,分别是ls -l +文件名、ls -l +目录,如下图所示:

        根据图片中得到的结果分析出,ls -l 命令得到的结果包含有文件类型,权限信息,链接数,用户名,组名,文件大小,最后修改时间,文件名称以及链接文件的目标文件。 

        故程序的设计要求就是通过用户传入一个文件名或者路径,解析出该文件或者该路径下所有文件的相关信息并按照格式打印到终端上。

功能实现

目录流

打开目录

#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
{
功能:打开一个目录;
参数:目录名称
返回值: 成功返回一个目录流指针,失败返回NULL;
}

读取目录

#include <dirent.h>
struct dirent *readdir(DIR *dirp);
{
功能:读取目录里面的文件信息;
参数:
dirp:目录流
返回值:成功返回下一个目录项, 失败或者读到目录的末尾返回
NULL,并设置错误号;
}

目录流的关闭

#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
返回值:成功返回0, 失败返回-1,并设置错误号;

获取文件的属性 

         stat这个结构体是用来描述一个linux系统文件系统中的文件属性的结构,stat结构体的详细属性如下:

struct stat {
        mode_t     st_mode;       //文件对应的模式,文件,目录等
        ino_t      st_ino;       //inode节点号
        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;     //文件内容对应的块数量
      };
int stat(const char *pathname, struct stat *statbuf);
如果获取的文件是符号链接文件,那么获取的是符号链接文件的目标的相
关属性
int fstat(int fd, struct stat *statbuf);
以文件描述符的方式来获取文件的属性
int lstat(const char *pathname, struct stat *statbuf);
如果获取的文件是符号链接文件,那么获取的是符号链接文件的本身的相
关属性
返回值:成功返回0, 失败返回-1并设置错误号;

定义一个struct stat sbuf;通过lstat(s,&sbuf)来获取文件属性,其中代表文件路径。 

文件的类型

stat结构体中的st_mode则 则定义了下列8个宏:
    S_IFMT   0170000     文件类型的位遮罩
    S_IFSOCK 0140000     scoket
    S_IFLNK  0120000     符号连接
    S_IFREG  0100000     一般文件
    S_IFBLK  0060000     区块装置
    S_IFDIR  0040000     目录
    S_IFCHR  0020000     字符装置
    S_IFIFO  0010000     先进先出

st-mode & S_IFMT 的结果即为七大文件类型的宏值之一;所以可以使用switch-case语句来判断文件的类型并进行打印。

//查看文件类型
	switch(sbuf.st_mode&S_IFMT){
		case S_IFSOCK:	printf("s");break;
		case S_IFCHR:	printf("c");break;
		case S_IFLNK:	printf("l");break;
		case S_IFREG:	printf("-");break;
		case S_IFBLK:	printf("b");break;
		case S_IFDIR:	printf("d");break;
		case S_IFIFO:	printf("p");break;	
	}

 另外, 上述的文件类型在POSIX中也定义了检查这些类型的宏定义:
    S_ISLNK (st_mode)    判断是否为符号连接
    S_ISREG (st_mode)    是否为一般文件
    S_ISDIR (st_mode)    是否为目录
    S_ISCHR (st_mode)    是否为字符装置文件
    S_ISBLK (s3e)        是否为先进先出
    S_ISSOCK (st_mode)   是否为socket

使用st_mode成员判断文件类型:
            if((sb.st_mode & S_IFMT) ==S_IFLNK)
            {   
                printf("连接文件\n");
            }      

            if (S_ISREG(sb.st_mode)) 
            {   
                printf("普通文件\n");
            }

 文件的权限

    S_IRUSR         00400             文件所有者具可读取权限            二进制:100 000 000        1<<8
    S_IWUSR        00200             文件所有者具可写入权限            二进制:010 000 000        1<<7
    S_IXUSR         00100             文件所有者具可执行权限            二进制:001 000 000        1<<6

    S_IRGRP         00040             用户组具可读取权限                   二进制:000 100 000        1<<5
    S_IWGRP        00020             用户组具可写入权限                   二进制:000 010 000        1<<4
    S_IXGRP         00010             用户组具可执行权限                   二进制:000 001 000        1<<3

    S_IROTH         00004             其他用户具可读取权限               二进制:000 000 100        1<<2
    S_IWOTH        00002             其他用户具可写入权限               二进制:000 000 010        1<<1
    S_IXOTH         00001             其他用户具可执行权限               二进制:000 000 001        1<<0

根据以上关系得出,st_mode &(1<<n)即为该文件的各个权限(0=<n<8=8)。

//查看权限
	int n=8;
	for(;n>=0;n--)
		if(sbuf.st_mode&(1<<n))
		    switch(n%3){
			case 2:	printf("r");break;
			case 1:	printf("w");break;
			case 0:	printf("x");break;
		}
		else{
			printf("-");
		}

文件的链接数 

通过stat结构体中st_nlink属性可以获取文件的链接数。

printf(" %d",sbuf.st_nlink);

 文件所属的用户、用户组

用户信息与用户组信息存储分别在struct stat中的st_uid和 st_gid中

getpwuid函数:解码所有者信息

#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd {
               char   *pw_name;       /* username */
               char   *pw_passwd;     /* user password */
               uid_t   pw_uid;        /* user ID */
               gid_t   pw_gid;        /* group ID */
               char   *pw_gecos;      /* user information */
               char   *pw_dir;        /* home directory */
               char   *pw_shell;      /* shell program */
           };

getgrgid函数:解码所属组信息

#include <sys/types.h>
#include <grp.h>

struct group *getgrgid(gid_t gid);
struct group {
               char   *gr_name;        /* group name */
               char   *gr_passwd;      /* group password */
               gid_t   gr_gid;         /* group ID */
               char  **gr_mem;         /* NULL-terminated array of pointers
                                          to names of group members */
           };

//查看用户名,组名
	struct passwd *puser;
	puser=getpwuid(sbuf.st_uid);
	printf(" %s",puser->pw_name);
	
	struct  group  *pgroup;
	pgroup=getgrgid(sbuf.st_gid);
	printf(" %s",pgroup->gr_name);

文件大小

文件大小存储在struct stat 结构体中st_size属性中 

//查看大小    
    printf(" %ld",sbuf.st_size);

 文件最后修改时间

文件的最后修改时间信息存储在struct stat 结构体中 st_mtime属性中  ,通过localtime解码时间信息。

struct tm *now_time;
	now_time=localtime(&sbuf.st_mtime);
	printf(" %d-%d-%d %d:%d"
		,now_time->tm_year+1900
		,now_time->tm_mon+1,now_time->tm_mday
		,now_time->tm_hour,now_time->tm_min);

文件名

//查看文件名
	printf(" %s",s);

链接文件的目标文件

        通过ssize_t readlink(const char *pathname, char *buf, size_t bufsiz)函数获取链接文件的目标文件
//链接指向目标文件
	char buf[128]={0};
	if((sbuf.st_mode&S_IFMT)==S_IFLNK)
	{
		readlink(s,buf,sizeof(buf));
		printf("-->%s\n",buf);
	}
	else
		printf("\n");	
	

程序设计

设计思想及源码

        主程序中使用命令行参数的方式传入文件名或目录,故需要分情况讨论,传入不同的参数执行不同的函数。

int main(int argc, char* argv[])
{
	if(argc!=2){
		printf("请输入两个参数!!!\n");
		return -1;
	}
	struct stat sbuf;
	lstat(argv[1],&sbuf);
	if(S_ISDIR(sbuf.st_mode))
	{	//如果是目录
		dirPath(argv[1]);
	}
	else
	{	//如果是文件名
		filePath(argv[1]);
	}
     	return 0;
}

如果传入的是文件名:

void filePath(const char* s)
{
	struct stat sbuf;
	lstat(s,&sbuf);
	//查看文件类型
	switch(sbuf.st_mode&S_IFMT){
		case S_IFSOCK:	printf("s");break;
		case S_IFCHR:	printf("c");break;
		case S_IFLNK:	printf("l");break;
		case S_IFREG:	printf("-");break;
		case S_IFBLK:	printf("b");break;
		case S_IFDIR:	printf("d");break;
		case S_IFIFO:	printf("p");break;	
	}
	//查看权限
	int n=8;
	for(;n>=0;n--)
		if(sbuf.st_mode&(1<<n))
		    switch(n%3){
			case 2:	printf("r");break;
			case 1:	printf("w");break;
			case 0:	printf("x");break;
		}
		else{
			printf("-");
		}
	//查看链接数
	printf(" %d",sbuf.st_nlink);
	//查看用户名,组名
	struct passwd *puser;
	puser=getpwuid(sbuf.st_uid);
	printf(" %s",puser->pw_name);
	
	struct  group  *pgroup;
	pgroup=getgrgid(sbuf.st_gid);
	printf(" %s",pgroup->gr_name);
	//查看大小	
	printf(" %ld",sbuf.st_size);
	//查看最后修改时间
	struct tm *now_time;
	now_time=localtime(&sbuf.st_mtime);
	printf(" %d-%d-%d %d:%d"
		,now_time->tm_year+1900
		,now_time->tm_mon+1,now_time->tm_mday
		,now_time->tm_hour,now_time->tm_min);
	//查看文件名
	printf(" %s",s);
	//链接指向目标文件
	char buf[128]={0};
	if((sbuf.st_mode&S_IFMT)==S_IFLNK)
	{
		readlink(s,buf,sizeof(buf));
		printf("-->%s\n",buf);
	}
	else
		printf("\n");	
	
}

如果传入的是目录:

        首先需要打开目录,然后依次读取目录中的文件进行文件属性获取然后打印,读取结束后关闭目录。

void dirPath(const char* s)
{
	
	DIR* dirp=opendir(s);
	if(dirp==NULL)
	{
		perror("opendir");
		return;
	}
	struct dirent* fp;
	while(NULL!=(fp=readdir(dirp)))
	{
		if(fp->d_name[0]=='.')
		{
			continue;
		}
	struct stat sbuf;
	lstat(fp->d_name,&sbuf);
	//查看文件类型
	switch(sbuf.st_mode&S_IFMT){
		case S_IFSOCK:	printf("s");break;
		case S_IFCHR:	printf("c");break;
		case S_IFLNK:	printf("l");break;
		case S_IFREG:	printf("-");break;
		case S_IFBLK:	printf("b");break;
		case S_IFDIR:	printf("d");break;
		case S_IFIFO:	printf("p");break;	
	}
	//查看权限
	int n=8;
	for(;n>=0;n--)
		if(sbuf.st_mode&(1<<n))
		    switch(n%3){
			case 2:	printf("r");break;
			case 1:	printf("w");break;
			case 0:	printf("x");break;
		}
		else{
			printf("-");
		}
	//查看链接数
	printf(" %d",sbuf.st_nlink);
	//查看用户名,组名
	struct passwd *puser;
	puser=getpwuid(sbuf.st_uid);
	printf(" %s",puser->pw_name);
	
	struct  group  *pgroup;
	pgroup=getgrgid(sbuf.st_gid);
	printf(" %s",pgroup->gr_name);
	//查看大小	
	printf(" %6ld",sbuf.st_size);
	//查看最后修改时间
	struct tm *now_time;
	now_time=localtime(&sbuf.st_mtime);
	printf(" %d-%d-%d %2d:%2d"
		,now_time->tm_year+1900
		,now_time->tm_mon+1,now_time->tm_mday
		,now_time->tm_hour,now_time->tm_min);
	//查看文件名
	printf(" %s",fp->d_name);
	//链接指向目标文件
	char buf[128]={0};
	if((sbuf.st_mode&S_IFMT)==S_IFLNK)
	{	//读取链接文件的指向
		readlink(fp->d_name,buf,sizeof(buf));
		printf("-->%s\n",buf);
	}
	else
		printf("\n");	
	}
	closedir(dirp);
}

实现效果

 可以看到对于文件名,./a.out   ./a.out和   ls -l  ./a.out  的效果一致

 对于目录,./a.out   ./  和  ls -l   ./的效果一致

;