实验六 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中打开终端文件的方式,由原来的阻塞变为非阻塞,观察运行结果和原来有什么不同。
八、参考资料
- 金国庆等,Linux程序设计(第二版),浙江大学出版社,2014年4月
- Neil Matthew,《Linux程序设计》(第4版), 人民邮电出版社,2014年9月
- 杨宗德,《Linux高级程序设计》(第三版),人民邮电出版社,2012年11月
- Daniel P.,《深入理解Linux内核》(第三版),中国电力出版社,2013年1月