linux实验6

实验六 Linux文件操作实验

学号:6130116217

专业班级:计算机科学与技术165班

课程名称:Linux程序设计实验

一、实验项目名称

Linux文件操作实验

二、实验目的

熟悉 Linux 文件 I/O 相关的应用开发,掌握 open()、read()、write()等函数的使用

三、实验基本原理

Linux 文件操作

四、主要仪器设备及耗材

硬件: PC机;
软件:Windows OS,VMware,Fedora10.0或其他Linux发行版。

五、实验步骤

1. 编写c程序,调用write, read, open等函数分别实现如下功能(要求进行必要的出错检查):

(1)创建一个文件testfile.txt,文件内容从键盘输入;
(2)将testfile.txt的内容显示在屏幕上,并将testfile.txt的内容复制到一个新文件file2.txt中。
vim 1.c

#include<stdio.h> /*文件预处理,包含标准输入输出库*/
#include<stdlib.h> /*文件预处理,包含system 函数库*/
#include<fcntl.h> /*文件预处理,包含open 函数库*/
#include<unistd.h>
char buf[1000];
char temp[1000];
int p,n;
int fdsrc,fddes;

int main () { /*C 程序的主函数,开始入口*/

        //从键盘读取
        printf("请输入要输入的信息,以回车结尾\n");
        n=read(STDIN_FILENO,buf,1000);
        if(n<0) {
                printf("read STDIN_FILENO\n");
                exit(1);
        }

        //输出到屏幕
        printf("你输入的信息是:\n");
        write(STDOUT_FILENO,buf,n);
        printf("\n");

        //写入文件5-6file.txt
        if((fdsrc=open("testfile.txt",O_CREAT|O_RDWR))<0) {
                /*选项O_TRUNC 表示文件存在时清空*/
                perror("打开文件testfile.txt出错\n");
                exit(1);
        } else {
                printf("打开(创建)文件“testfile.txt”,文件描述符为:%d\n",fdsrc);
        }
        write(fdsrc,buf,n);
        if(close(fdsrc)<0) {
                printf("关闭文件testfile.txt出错\n");
                exit(1);
        }
        printf("内容已存入testfile.txt\n");

        //复制到file2.txt
        if((fddes=open("file2.txt",O_CREAT|O_RDWR))<0) {
                /*选项O_TRUNC 表示文件存在时清空*/
                printf("打开文件file2.txt出错\n");
                exit(1);
        } else {
                printf("打开(创建)文件“file2.txt”,文件描述符为:%d\n",fddes);
        }
        if((fdsrc=open("testfile.txt",O_CREAT|O_RDWR))<0) {
                /*选项O_TRUNC 表示文件存在时清空*/
                perror("打开文件testfile.txt出错\n");
                exit(1);
        } else {
                printf("打开(创建)文件“testfile.txt”,文件描述符为:%d\n",fdsrc);
        }
        //  while(n=read(fdsrc,temp,1)>0)
        //  {
        //      write(fddes,temp,n);
        //      printf("%d\n",n);
        // }
        read(fdsrc,temp,n);
        write(fddes,temp,n);

        if(close(fdsrc)<0) {
                printf("关闭文件testfile.txt出错\n");
                exit(1);
        }

        if(close(fddes)<0) {
                printf("关闭文件file2.txt出错\n");
                exit(1);
        }
        printf("内容已复制到file2.txt\n");
        return 0;
}

编译
在这里插入图片描述
运行
在这里插入图片描述
查看file2.txt和testfile.txt内容
在这里插入图片描述

2.调试教材中例5.9,例5.18程序
①例5.9

vim 2.c

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "timeout\n"
int main(void)
{
    char buf[10];
    int fd, n, i, j;
    while(1)
    {
        fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
        if(fd<0)
        {
            perror("open /dev/tty");
            exit(1);
        }
        for(i=0; i<5; i++)
        {
            n = read(fd, buf, 10);
            if(n>=0)
                break;
            if(errno!=EAGAIN)
            {
                perror("read /dev/tty");
                exit(1);
            }
            sleep(6);
            write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
        }
        if(i==5)
            write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
        else
            write(STDOUT_FILENO, buf, n);
            for(i=0; i<5; i++)
        {
            for(j=0; j<=i; j++)
                printf("%2c",'*');
            printf("\n");
        }
        close(fd);
        return 0;
    }
}

编译
在这里插入图片描述
运行
在这里插入图片描述
在这里插入图片描述

②例5.18

vim 22.c

#include<stdio.h>
#include<time.h>
#include<linux/types.h>
#include<dirent.h>
#include<sys/stat.h>
#include<unistd.h>
#include<string.h>
char *wday[]= {"日","一","二","三","四","五","六"};
void list(char *name,int suojin)
{
    DIR *dirname;
    struct dirent *content;
    struct stat sb;
    struct tm *ctime;
    int i;
    if((dirname=opendir(name))==NULL)
    {
        printf("该目录不存在\n");
        return;
    }
    chdir(name);/*改换工作目录*/
    while((content=readdir(dirname))!=NULL)
    {
        for(i=0; i<suojin; i++)
            putchar('\t');
        if(content->d_type==4)
            printf("目录\t");
        else if(content->d_type==8)
            printf("文件\t");
        else
            printf("其他\t");
        stat(content->d_name,&sb);
        ctime=gmtime(&sb.st_mtime);
        printf("%d 年%d 月%d 日星期%s %d:%d:%d\t",ctime->tm_year+1900,
               1+ctime->tm_mon,ctime->tm_mday,wday[ctime->tm_wday],ctime->tm_hour,
               ctime->tm_min,ctime->tm_sec);
        printf("%d\t",sb.st_size);
        printf("%s\n",content->d_name);/*列出目录或文件的相关信息*/
        if(content->d_type==4&&strcmp(content->d_name,"..")&&strcmp(content->d_name,"."))
        {
            list(content->d_name,suojin+1);/*如果是目录,则递归列出目录里的内容*/
        }
    }
    closedir(dirname);
    chdir("..");/*当该层目录中的文件列完后,返回父目录*/
}
int main(int argc,char *argv[])
{
    char name[256];
    printf("类型\t 最后修改时间\t\t\t 大小\t 文件名\n");
    printf("*******************************************************\n");
    if(argc==1)
    {
        printf("Enter directory name:");
        scanf("%s",name);
        list(name,0);
    }
    else
    {
        list(argv[1],0);
    }
}

编译
在这里插入图片描述
运行
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 编写c程序myls,实现命令ls在不同参数(-l, -a, 以及-R)下的功能。

编写两个文件list.h和myls.c文件放入linux下同一目录下,gcc编译,之后就可以通过./可执行文件名 -选项 参数运行了。本程序实现大部分ls的功能。
vim myls.c

/*
    该文件简单模拟linux系统的ls命令(实现了大部份功能)


功能需求(FIXME),实现的常用的选项:
    --help  显示帮助信息
    -a      显示所有文件(即包括隐藏文件)
    -l      显示详细信息
    -L      只打印链接文件名
    -r      反排序
    -R      递归显示,即遇到目录会再次进入目录把文件列举出来
    -c      以文件最后状态修改时间排序,st_ctime; //time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、 权限被更改时更新 
    -t      以文件最后修改时间排序,st_mtime; //time of last modification 文件最后一次被修改的时间, 一般只有在用mknod、 utime 和write 时才会改变
    -u      以文件最后访问时间排序,st_atime; //time of lastaccess 文件最近一次被存取或被执行的时间, 一般只有在用mknod、 utime、read、write 与tructate 时改变
    -S      以文件大小排序
    -n      以文件名排序(以文件名排序,是默认排序,如果有多种排序也只采用文件名排序)
    -i      显示I节点
	-h      –human-readable 以容易理解的格式列出文件大小 (例如 1K 234M 2G)

FIXME:
    已知BUG:
    (1) 输出的格式与系统由一定差别
	(2) 文件与文件夹没有颜色区分
*/

#include <time.h>
#include <stdio.h>
#include <getopt.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdlib.h>
#include <assert.h>
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include "list.h"


#define MAX_PATH            256             /*文件或者目录名的最大长度*/
#define PEER_MALLOC_FILE    64              /*当内存不够时,最少申请多少个struct file_info的大小
                                            避免频繁申请内存*/
/*  sys/stat.h 中struct stat结构体*/
/*
 struct stat {
    dev_t            st_dev; //device 文件的设备编号
    ino_t            st_ino; //inode 文件的i-node
    mode_t           st_mode; //protection 文件的类型和存取的权限
    nlink_t          st_nlink; //number of hard links 连到该文件的硬连接数目, 刚建立的文件值为1.
    uid_t            st_uid; //user ID of owner 文件所有者的用户识别码 
    gid_t            st_gid; //group ID of owner 文件所有者的组识别码 
    dev_t            st_rdev; //device type 若此文件为装置设备文件, 则为其设备编号 
    off_t            st_size; //total size, in bytes 文件大小, 以字节计算 
    unsigned long    st_blksize; //blocksize for filesystem I/O 文件系统的I/O 缓冲区大小. 
    u nsigned long   st_blocks; //number of blocks allocated 占用文件区块的个数, 每一区块大小为512 个字节. 
    time_t           st_atime; //time of lastaccess 文件最近一次被存取或被执行的时间, 一般只有在用mknod、 utime、read、write 与tructate 时改变.
    time_t           st_mtime; //time of last modification 文件最后一次被修改的时间, 一般只有在用mknod、 utime 和write 时才会改变
    time_t           st_ctime; //time of last change i-node 最近一次被更改的时间, 此参数会在文件所有者、组、 权限被更改时更新 
}; 								
*/														
											
/*一个文件基本信息的节点*/
struct file_info
{
    char                fil_name[MAX_PATH];     /* 文件名节点 */
    char                dst_name[MAX_PATH];     /* 链接文件名真实名 */
    char                is_link;                /* 是否是链接文件 1:是 0:否 */
    struct stat         statbuf;                /*文件属性节点*/
};

/*一个目录的信息列表*/
struct dir_info
{
    char                dir_path[MAX_PATH];     /*目录名节点*/
    struct file_info    *p_filenode;             /*指向该目录下的文件信息
                                                内存类似于数组,之所以不采用链表,是为了好用qsort进行排序*/
    size_t              used_size;              /*已用了多少个strcut file_info*/
    size_t              free_size;              /*空余还有多少个strcut file_info*/
    size_t              dir_size;               /*该目录的大小(单位为k),用ls -l显示的第一行 total(总用量)的值*/
    char                need_print_total;       /*该目录是否需要打印total*/
};

/*等待遍历节点的节点*/
struct dir_list
{
    char                dir_path[MAX_PATH];     /*待遍历的目录节点*/
    struct list_head    list_node;              /*链表节点*/
};

struct param
{
    char            a;      /* 显示所有文件 */
    char            l;      /* 显示详细信息 */
    char            L;      /* 只显示链接文件 */
    char            r;      /* 反排序 */
    char            R;      /* 递归显示 */
    char            c;      /* 状态修改时间排序*/
    char            t;      /* 最后修改时间排序*/
    char            u;      /* 最后访问时间排序*/
    char            S;      /* 以文件大小排序*/
    char            n;      /* 以文件名排序*/
    char            i;      /* 显示I节点*/
    char            d;      /* 显示每块大小*/
	char            h;       /* 以k为单位显示*/
    char            m;      /* 显示帮助信息*/
};
//全局
typedef int (*COM_FUNC)(const void *, const void *);//定义了一个指向函数的指针COM_FUNC,其返回值int类型,参数也是后面的(const void *),接下来我们就可以直接使用COM_FUNC来定义这种指针变量,比如:COM_FUNC g_com_func; //等价于int g_com_func(const void *,const void *);

struct dir_info         g_dir_info;             /*当前正处理的目录*/
struct param            g_param;                /*传入参数*/
struct list_head        g_dir_head;             /*需要扫描目录的链表头*/
COM_FUNC                g_com_func = NULL;

/*帮助信息*/
static void printf_usage()
{
    printf("usage:\n");
    printf("--help          show the help infomation\n");
    printf("-a              show all files\n");
    printf("-l              show the detailed information\n");
    printf("-L              only show linkname if the file is linkfile\n");
    printf("-r              recursive display\n");
	printf("-R              Recursive display\n");
    printf("-c              sort by status change\n");
    printf("-t              sort by last modify time\n");
    printf("-u              sort by last access time\n");
    printf("-S              sort by file size\n");
	printf("-h              –human-readable display\n");
    printf("-n              sort by file name, the default.And if input more than one sort way, only sort by file name\n");
    printf("-i              show i-node info\n");
    printf("-d              show I/O block size\n");
}

/*计算一个目录的大小,即ls -l显示出来的第一行 "total" */
static void cal_dir_size()
{
    size_t              i;
    struct file_info    *p;

    g_dir_info.dir_size = 0;
    g_dir_info.need_print_total = 1;    /*需要打印total字段标志*/
    for(i = 0; i < g_dir_info.used_size; ++i)  //used_size为size_t类型
    {
        p = g_dir_info.p_filenode + i;
        g_dir_info.dir_size += p->statbuf.st_blksize * p->statbuf.st_blocks;//文件系统的I/O缓冲区大小  占用文件区块的个数, 每一区块大小为512 个字节
    }
    /*换算为k*/
    g_dir_info.dir_size = g_dir_info.dir_size/1024/8;
}
/*
    程序带错误码退出
*/
static void dead_errno(int no)
{
    assert(0);//在assert.h中,assert的作用是现计算表达式expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
    exit(no);
}

/*FIXME 内存追加方式申请内存*/
static void realloc_file_info(struct dir_info *dir_info)
{
    assert(dir_info != NULL);
    assert(dir_info->free_size == 0);   /*没空闲内存了才会申请*/
    size_t              newsize;        /*新内存的大小*/

    newsize = (dir_info->used_size + PEER_MALLOC_FILE)*sizeof(struct file_info);
    if((dir_info->p_filenode = realloc(dir_info->p_filenode, newsize)) == NULL)
    {
		/*
		 *perror()一般输出错误原因,参数为字符串
		 */
        perror("realloc");
        dead_errno(1);
    }

    dir_info->free_size += PEER_MALLOC_FILE;
}

/*保存文件节点信息*/
static void save_file_info(struct dir_info *dir_info, struct stat *stat, char *d_name, char *dst_name)
{
    assert(d_name != NULL && d_name[0] != 0);
    struct file_info        *pfile_info = NULL;
    assert(dir_info != NULL && stat != NULL);

    if(dir_info->p_filenode == NULL || dir_info->free_size == 0)/*内存不够*/
        realloc_file_info(dir_info);

    pfile_info = dir_info->p_filenode + dir_info->used_size;
    strncpy(pfile_info->fil_name, d_name, MAX_PATH-1);
    memmove(&pfile_info->statbuf, stat, sizeof(struct stat));
    if(dst_name != NULL)     /*d_name是链接文件*/
    {
        pfile_info->is_link = 1;
        strncpy(pfile_info->dst_name, dst_name, MAX_PATH-1);
    }
    else
    {
        pfile_info->is_link = 0;
        pfile_info->dst_name[0] = 0;
    }

    --(dir_info->free_size);
    ++(dir_info->used_size);
}

/* 添加目录 */
static void add_dir(char *pdir, struct list_head *head)
{
    assert(pdir != NULL && pdir[0] != 0 && head != NULL);
    struct dir_list         *pnode = NULL;

    if((pnode = (struct dir_list*)malloc(sizeof(struct dir_list))) == NULL)
    {
        printf("malloc dir_list error\n");
        exit(1);
    }
    memset(pnode, 0, sizeof(struct dir_list));
    strncpy(pnode->dir_path, pdir, MAX_PATH-1);
    list_add_tail(&pnode->list_node, head);     //插入尾部
}

/*
    以最后状态修改时间排序函数
*/
static int cmp_last_statetime(const void *arg1, const void *arg2)
{
    struct file_info   *p1 = (struct file_info*)arg1;
    struct file_info   *p2 = (struct file_info*)arg2;

    return p2->statbuf.st_ctime - p1->statbuf.st_ctime;
}


/*
    以文件最后修改时间排序函数
*/
static int cmp_last_change(const void *arg1, const void *arg2)
{
    struct file_info   *p1 = (struct file_info*)arg1;
    struct file_info   *p2 = (struct file_info*)arg2;

    return p2->statbuf.st_mtime - p1->statbuf.st_mtime;
}

/*
    以文件最后访问时间排序函数
*/
static int cmp_last_accesstime(const void *arg1, const void *arg2)
{
    struct file_info   *p1 = (struct file_info*)arg1;
    struct file_info   *p2 = (struct file_info*)arg2;

    return p2->statbuf.st_atime - p1->statbuf.st_atime;
}

/*
    以文件大小排序函数
*/
static int cmp_filesize(const void *arg1, const void *arg2)
{
    struct file_info   *p1 = (struct file_info*)arg1;
    struct file_info   *p2 = (struct file_info*)arg2;

    return p2->statbuf.st_size - p1->statbuf.st_size;
}

/*
    以文件名排序函数
*/
static int cmp_filename(const void *arg1, const void *arg2)
{
    struct file_info   *p1 = (struct file_info*)arg1;
    struct file_info   *p2 = (struct file_info*)arg2;

    return strncasecmp(p1->fil_name, p2->fil_name, MAX_PATH);
}

/* 排序 */
static void sort(struct dir_info *dir_info)
{
    /*TODO*/
    assert(dir_info != NULL);
    if(g_com_func != NULL)
		/* 快速排序void qsort(void * base,size_t nmemb,size_t size ,int(*compar)(const void *,const void *))数组首地址 数组元素个数 元素占用内存空间 指向函数的指针 */
        qsort(dir_info->p_filenode, dir_info->used_size,sizeof(struct file_info), g_com_func);
}


/* 
LINUX下历遍目录的方法:
打开目录-》读取-》关闭目录
相关函数是
opendir -> readdir -> closedir

opendir()成功则返回DIR* 型态的目录流,打开失败则返回NULL.
#include <dirent.h>
DIR *opendir(const char *dirname);

readdir()成功则返回下个目录进入点。有错误发生或读取到目录文件尾则返回NULL
#include <dirent.h>
struct dirent *readdir(DIR *dirp);

关闭成功则返回0,失败返回-1,错误原因存于errno 中
#include <dirent.h>
int closedir(DIR *dirp);
*/

/* 
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字符  
} 
*/

/*
    扫描一个目录下的所有文件
*/
static int scan_adir(char *dir_name, struct dir_info *dir_info)
{
    assert(dir_name != NULL);
    assert(dir_info != NULL);
    assert(strlen(dir_name) > 0);
	
    struct stat             statbuf;//定义一个struct stat结构体变量
    struct dirent           *dirp;  //定义一个struct dirent结构体指针变量
    DIR                     *dp;    
    char                    *ptr;   /*指向目录名的最后*/
    char                    filename[MAX_PATH] = {0};

	//错误检测处理
    if(lstat(dir_name, &statbuf) < 0)
    {
        perror("lstat");
        dead_errno(1);
    }
    if(S_ISDIR(statbuf.st_mode) == 0) /* 不是目录 */
    {
        printf("%s is not a directory\n", dir_name);
        assert(0);
    }

    strcpy(filename, dir_name);
    ptr = filename+strlen(dir_name);
    /*使目录始终带有/结束*/
    if(ptr[-1] != '/')
    {
        ptr[0] = '/';
        ++ptr;
    }

    /*保存该目录名*/
    strncpy(dir_info->dir_path, filename, MAX_PATH-1);

    /*下面是目录了*/
    if((dp = opendir(dir_name)) == NULL)
    {
        printf("opendir %s error: %s\n", dir_name, strerror(errno));
        return 1;
    }

    while((dirp = readdir(dp)) != NULL)
    {
        /*不显示所有信息,跳过隐藏文件(包括. ..)*/
        char a[255];
        strcpy(a,dirp->d_name);
//        printf("%c\n",a[0]);
        if(g_param.a == 0 &&(a[0]=='.' ||strcmp(dirp->d_name, ".") == 0 ||strcmp(dirp->d_name, "..") == 0))
            continue;
		
        /*拼接文件名*/
        strcpy(ptr, dirp->d_name);
        /*获取文件属性*/
        if(lstat(filename, &statbuf) < 0)
        {
            printf("lstat2 file %s error: %s\n", ptr, strerror(errno));
            continue;
        }
        if(S_ISLNK(statbuf.st_mode)) /*链接文件*/
        {
            char            dst_name[MAX_PATH] = {0};
            if(readlink(dirp->d_name, dst_name, MAX_PATH-1) < 0)
            {
                printf("readlink file %s error: %s\n", dirp->d_name, strerror(errno));
                continue;
            }
            save_file_info(dir_info, &statbuf, dirp->d_name, dst_name);
        }
        else
        {
            if(g_param.R && S_ISDIR(statbuf.st_mode))
                add_dir(filename, &g_dir_head);
            save_file_info(dir_info, &statbuf, dirp->d_name, NULL);
        }
    }

    closedir(dp);
    /*排序*/
    sort(dir_info);
    return 0;
}

/*
    得到用户选项参数
    FIXME:
        当用户输入的选项有冲突时,还没做错误性检测
	getopt被用来解析命令行选项参数
*/
static int get_param(int argc, char *argv[])
{
    char            c;

    /*长选项--(第一项:名字,第二项:是否带参数0表示不带参数, 第三项:填0,第四项:短选项名)*/
    /*返回第四个数(如果第三个数为NULL,不然就返回0)*/
    static struct option long_options[] =
    {
        {"help", 0, 0, 'm'},//getopt_long返回'm'
        {0, 0, 0, 0}        
    };
    /*短参数定义-*/
    static const char short_options[] = "alLrRctuSidnh";//-l -h -r -t -R...

    while(1)
    {

        int option_index = 0;
        c = getopt_long(argc, argv,short_options,long_options,&option_index);//getopt_long既可以处理长选项,也可以处理短选项,无选项后返回-1
		
        if(c == -1)
            break;

        switch(c)
        {
            case 'm':               /*长参数返回的,就只显示帮助信息*/
                g_param.m = 1;
                return -1;          /*返回假出错信息,让上层显示帮助信息*/
			case 'h':
				g_param.h=1;
				break;
             case 'a':
                g_param.a = 1;
                break; 
            case 'l':
                g_param.l = 1;
                break;
             case 'L':
                g_param.L = 1;
                break; 
            case 'r':
                g_param.r = 1;
                break;
            case 'R':
                g_param.R = 1;
                break;
             case 'c':
                g_param.c = 1;
                break; 
            case 't':
                g_param.t = 1;
                break;
             case 'u':
                g_param.u = 1;
                break;
            case 'S':
                g_param.S = 1;
                break;
            case 'n':
                g_param.n = 1;
                break;
            case 'i':
                g_param.i = 1;
                break;
            case 'd':
                g_param.d = 1;
                break; 
            case '?':
//                printf("unknown param: %s\n", optarg);
                return -2;
            default:
                printf ("?? getopt returned character code %d \n", c);
                return -3;
        }
    }


    /*校验只能用一种排序方式*/
    {
        int         total = 0;
        if(g_param.c == 1)
            ++total;
        if(g_param.t == 1)
            ++total;
        if(g_param.u == 1)
            ++total;
        if(g_param.S == 1)
            ++total;
        if(g_param.n == 1)
            ++total;

        g_com_func = cmp_filename;  /*先置为默认排序方式   以文件名排序*/

        if(total > 1)
        {
            printf("the param sort param error,now sort by filename\n");
        }
        else if(total == 1)     /* 仅在此情况下,才有可能排序方式要变 */
        {
            if(g_param.c) g_com_func = cmp_last_statetime;       /*以最后状态修改时间排序 */
            else if(g_param.t) g_com_func = cmp_last_change;     /*以文件最后修改时间排序 */
            else if(g_param.u) g_com_func = cmp_last_accesstime; /*以文件最后访问时间排序 */
            else if(g_param.S) g_com_func = cmp_filesize;        /*以文件大小排序         */
        }
    }

    /*校验后面是否还有没解析的参数,即为需要查看的文件或者目录*/
    if (optind < argc)//optind储存第一个不包含选项的命令行参数
    {
        struct stat             statbuf;//定义一个结构体变量,在#include <sys/stat.h>中
        while(optind < argc)
        {
            if(lstat(argv[optind], &statbuf) < 0)//int lstat(const char *path, struct stat *buf); path:文件路径名 buf:保存文件信息的结构体,成功执行返回0,失败返回-1
            {
                printf("lstat file '%s' error: %s\n", argv[optind++], strerror(errno));
                exit(-1);
            }

            if(S_ISDIR(statbuf.st_mode))//是否为目录
                add_dir(argv[optind++], &g_dir_head);//添加目录
            else if(S_ISLNK(statbuf.st_mode))//是否为符号链接
            {
                char            dst_name[MAX_PATH] = {0};
                if(readlink(argv[optind], dst_name, MAX_PATH-1) < 0)//#include <unistd.h>,int readlink(const  char *path,  char *buf, size_t  bufsiz);
                    continue;
                save_file_info(&g_dir_info, &statbuf, argv[optind++], dst_name);
            }
            else
            {
                save_file_info(&g_dir_info, &statbuf, argv[optind++], NULL);
            }
        }
    }
    else
    {
        add_dir("./", &g_dir_head); /*如果不带文件,则默认为当前目录*/
    }

    return 0;
}

/*初始化*/
static void init()
{
    memset(&g_dir_info, 0, sizeof(struct dir_info));//memset可以方便的清空一个结构类型的变量
    memset(&g_param, 0, sizeof(struct param));
    INIT_LIST_HEAD(&g_dir_head);
}


/* 打印文件的详细信息 */
static void _print_detail(struct file_info *info)
{
    int             n;
    char            timebuf[128] = {0};
    struct passwd   *ppasswd = NULL;    /*跟用户相关*/
    struct group    *pgroup = NULL;     /*跟用户组相关*/

    /*打印i节点号*/
    if(g_param.i&&g_param.l)
        printf("%-10d", (int)(info->statbuf.st_ino));

/*
st_mode:
1、S_IFMT 0170000 文件类型的位遮罩 
2、S_IFSOCK 0140000 scoket
3、S_IFLNK 0120000 符号连接 
4、S_IFREG 0100000 一般文件 
5、S_IFBLK 0060000 区块装置 
6、S_IFDIR 0040000 目录 
7、S_IFCHR 0020000 字符装置 
8、S_IFIFO 0010000 先进先出 
9、S_ISUID 04000 文件的 (set user-id on execution)位 
10、S_ISGID 02000 文件的 (set group-id on execution)位 
11、S_ISVTX 01000 文件的sticky 位 
12、S_IRUSR (S_IREAD) 00400 文件所有者具可读取权限 
13、S_IWUSR (S_IWRITE)00200 文件所有者具可写入权限 
14、S_IXUSR (S_IEXEC) 00100 文件所有者具可执行权限 
15、S_IRGRP 00040 用户组具可读取权限          u:r
16、S_IWGRP 00020 用户组具可写入权限          u:w
17、S_IXGRP 00010 用户组具可执行权限          u:x
18、S_IROTH 00004 其他用户具可读取权限        o:r
19、S_IWOTH 00002 其他用户具可写入权限        o:w
20、S_IXOTH 00001 其他用户具可执行权限上述的文件类型在 POSIX 中定义了检查这些类型的宏定义 o:x
21、S_ISLNK (st_mode) 判断是否为符号连接 
22、S_ISREG (st_mode) 是否为一般文件 
23、S_ISDIR (st_mode) 是否为目录 
24、S_ISCHR (st_mode) 是否为字符装置文件 
25、S_ISBLK (s3e) 是否为先进先出 
26、S_ISSOCK (st_mode) 是否为socket 若一目录具有sticky 位 (S_ISVTX), 则表示在此目录下的文件只能 被该文件所有者、此目录所有者或root 来删除或改名. 
*/	
    /*打印文件类型*/
    if(S_ISREG(info->statbuf.st_mode))
        putchar('-');
    else if(S_ISDIR(info->statbuf.st_mode))
        putchar('d');
    else if(S_ISCHR(info->statbuf.st_mode))
        putchar('c');
    else if(S_ISBLK(info->statbuf.st_mode))
        putchar('b');
    else if(S_ISFIFO(info->statbuf.st_mode))
        putchar('f');
    else if(S_ISLNK(info->statbuf.st_mode))
        putchar('l');
    else if(S_ISSOCK(info->statbuf.st_mode))
        putchar('s');
    else
        putchar('?');

    /*移位的方法比通过宏操作,代码要简洁些*/
    for(n = 8; n >= 0; --n)
    {
        if(info->statbuf.st_mode & (1 << n))
        {
            switch(n%3)
            {
                case 2:
                    putchar('r');
                    break;
                case 1:
                    putchar('w');
                    break;
                case 0:
                    putchar('x');
                    break;
            }
        }
        else
            putchar('-');
    }

    putchar(' ');

    /*打印硬连接数*/
    printf("%-2d", (int)(info->statbuf.st_nlink));

    /*打印用户ID*/
    if((ppasswd = getpwuid(info->statbuf.st_uid)) != NULL)
        printf("%-13.13s", ppasswd->pw_name);
    else
        printf("%-5d", info->statbuf.st_uid);
    /*打印用户组ID*/
    if((pgroup = getgrgid(info->statbuf.st_gid)) != NULL)
        printf("%-13.13s", pgroup->gr_name);
    else
        printf("%-5d", info->statbuf.st_gid);
    /*打印文件大小*/
    if(g_param.h==1)
        printf("%5.1fk ", (double)((info->statbuf.st_size)/1024.0));
    else  
        printf("%-6d ",(int)(info->statbuf.st_size));

    /*按需打印块大小*/
    if(g_param.d)
        printf("%-6d", (int)(info->statbuf.st_blksize));
    /*打印时间*/
    ctime_r(&(info->statbuf.st_mtime), timebuf);//等价于asctime_r(localtime(timer),buf)
    if(timebuf[0] != 0)
        timebuf[strlen(timebuf)-1] = 0; /*去掉'\n'*/
    printf("%-20.20s", timebuf);
    /*打印文件名*/
    printf("%s", info->fil_name);

    if(info->is_link && (g_param.L == 0))
        printf(" -> %s", info->dst_name);

    putchar('\n');

}


static void _printf_data2(struct file_info *pfile)
{
    if(g_param.l)   /*长列表方式 显示详细信息*/
    {
        _print_detail(pfile);
    }
    else if(g_param.i)            /*短列表方式 */
    {
        printf("%d %s  ",(int)(pfile->statbuf.st_ino),pfile->fil_name);  //显示I节点和文件名
    }
    else                        //只显示文件名
    {
        printf("%s  ",pfile->fil_name);
    }
}

/*仅仅打印一个目录信息*/
static void _printf_data()
{
    size_t                  i;//unsigned int 类型,无符号,它的取值没有负数。用来表示 参数/数组元素个数,sizeof 返回值,或 str相关函数返回的 size 或 长度。sizeof 操作符的结果类型是size_t。

    /* 按需要打印目录 */
    if((g_param.l && g_param.R)||g_param.R)
        printf("%s\n", g_dir_info.dir_path);

    /*  按需要打印total字段 */
    if(g_param.l && g_dir_info.need_print_total)
    {
        if(g_param.h==1) printf("total %dk\n",(int)(g_dir_info.dir_size));
        else printf("total %d\n",(int)(g_dir_info.dir_size));
	}

    if(g_param.r)    /*反排序*/
    {
        for(i = g_dir_info.used_size; i > 0; --i)       /*note i是无符号数,不能用i >= 0来判断*/
            _printf_data2(g_dir_info.p_filenode + i - 1);   /*换成数组下标,要减1*/
    }
    else
    {
        for( i = 0; i < g_dir_info.used_size; ++i)
            _printf_data2(g_dir_info.p_filenode + i);
    }


}

/*释放一个目录占有的内存信息,并将目录信息进行初始化*/
static void _freedir2init()
{
    if(g_dir_info.p_filenode != NULL)
        free(g_dir_info.p_filenode);

    memset(&g_dir_info, 0, sizeof(struct dir_info));
}

/*打印一个目录,并重新初始化目录信息*/
static void print_data2free_dir()
{

    _printf_data();   //打印一个目录信息
    _freedir2init();  //释放一个目录占有的内存信息,并将目录信息进行初始化
}


static struct dir_list* get_adir()//get_adir()返回一个struct dir_list类型的指针
{
    if(list_empty(&g_dir_head))//判断是否为空
        return NULL;

    return list_entry(g_dir_head.next, struct dir_list, list_node); //获取节点地址
}

/*循环看是否有目录需要遍历*/
static void recur()
{
    struct dir_list             *pdir;

    while((pdir = get_adir()) != NULL)
    {
        assert(pdir != NULL && pdir->dir_path[0] != 0);
        scan_adir(pdir->dir_path, &g_dir_info);//扫描目录中所有文件
        cal_dir_size();             /*是完整目录就要计算目录大小了*/
        print_data2free_dir();        //打印目录信息
        list_del(&(pdir->list_node));//此节点操作完毕,删除此节点
        free(pdir->dir_path);        //释放内存
        printf("\n");
    }
}

int main(int argc, char *argv[])
{
    int err;

    init();

    if((err = get_param(argc, argv)) < 0)//小于0则打印帮助信息,get_param()得到命令行参数
    {
		if(err==-1) printf_usage();//打印帮助信息
		else printf("Try '--help' for more information.\n");
        return err;    //执行结束
    }

    print_data2free_dir();//打印一个目录信息

    recur();//循环看是否有目录需要遍历

    return 0;
}

vim list.h

#ifndef _LINUX_LIST_H
#define _LINUX_LIST_H



/*
 * Simple doubly linked list implementation.
 *
 * Some of the internal functions ("__xxx") are useful when
 * manipulating whole lists rather than single entries, as
 * sometimes we already know the next/prev entries and we can
 * generate better code by using them directly rather than
 * using the generic single-entry routines.
 */

struct list_head {
	struct list_head *next, *prev;
};

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
	struct list_head name = LIST_HEAD_INIT(name)

#define INIT_LIST_HEAD(ptr) do { \
	(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)

/*
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static __inline__ void __list_add(struct list_head * new,
	struct list_head * prev,
	struct list_head * next)
{
	next->prev = new;
	new->next = next;
	new->prev = prev;
	prev->next = new;
}

/**
 * list_add - add a new entry
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 */
static __inline__ void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

/**
 * list_add_tail - add a new entry
 * @new: new entry to be added
 * @head: list head to add it before
 *
 * Insert a new entry before the specified head.
 * This is useful for implementing queues.
 */
static __inline__ void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

/*
 * Delete a list entry by making the prev/next entries
 * point to each other.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 */
static __inline__ void __list_del(struct list_head * prev,
				  struct list_head * next)
{
	next->prev = prev;
	prev->next = next;
}

/**
 * list_del - deletes entry from list.
 * @entry: the element to delete from the list.
 * Note: list_empty on entry does not return true after this, the entry is in an undefined state.
 */
static __inline__ void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = entry->prev = 0;
}

/**
 * list_del_init - deletes entry from list and reinitialize it.
 * @entry: the element to delete from the list.
 */
static __inline__ void list_del_init(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	INIT_LIST_HEAD(entry);
}

/**
 * list_empty - tests whether a list is empty
 * @head: the list to test.
 */
static __inline__ int list_empty(struct list_head *head)
{
	return head->next == head;
}

/**
 * list_splice - join two lists
 * @list: the new list to add.
 * @head: the place to add it in the first list.
 */
static __inline__ void list_splice(struct list_head *list, struct list_head *head)
{
	struct list_head *first = list->next;

	if (first != list) {
		struct list_head *last = list->prev;
		struct list_head *at = head->next;

		first->prev = head;
		head->next = first;

		last->next = at;
		at->prev = last;
	}
}

/**
 * list_entry - get the struct for this entry
 * @ptr:	the &struct list_head pointer.
 * @type:	the type of the struct this is embedded in.
 * @member:	the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member) \
	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

/**
 * list_for_each	-	iterate over a list
 * @pos:	the &struct list_head to use as a loop counter.
 * @head:	the head for your list.
 */
#define list_for_each(pos, head) \
	for (pos = (head)->next; pos != (head); \
        	pos = pos->next)

/**
 * list_for_each_safe	-	iterate over a list safe against removal of list entry
 * @pos:	the &struct list_head to use as a loop counter.
 * @n:		another &struct list_head to use as temporary storage
 * @head:	the head for your list.
 */
#define list_for_each_safe(pos, n, head) \
	for (pos = (head)->next, n = pos->next; pos != (head); \
		pos = n, n = pos->next)


#endif

编译
在这里插入图片描述
运行
./ls -a
在这里插入图片描述
./ls -l
在这里插入图片描述
./ls -R
在这里插入图片描述
./ls -i
在这里插入图片描述
其他命令
在这里插入图片描述

六、实验数据及处理结果

七、思考讨论题

修改例5.9中打开终端文件的方式,由原来的阻塞变为非阻塞,观察运行结果和原来有什么不同。

八、参考资料

  1. 金国庆等,Linux程序设计(第二版),浙江大学出版社,2014年4月
  2. Neil Matthew,《Linux程序设计》(第4版), 人民邮电出版社,2014年9月
  3. 杨宗德,《Linux高级程序设计》(第三版),人民邮电出版社,2012年11月
  4. Daniel P.,《深入理解Linux内核》(第三版),中国电力出版社,2013年1月
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值