万物皆文件
文件的概念和类型
文件:一组相关数据的有序集合
文件类型:
类型 | 标识符 |
---|---|
块设备文件 | b |
字符设备文件 | c |
目录文件 | d |
常规文件 | - |
符号链接文件 | l |
套接字文件 | s |
管道文件 | p |
不同操作系统支持文件不一
文件I/O
文件I/O:由POSIX(可移植操作系统接口)定义的一些函数
无缓冲,每次读写操作都引起系统调用
特点:
- 核心概念是文件描述符
- 可访问各种类型文件
- 标准I/O基于文件I/O实现
文件描述符
- 每个打开的文件都对应一个文件描述符
- 文件描述符是一个非负整数。linux为程序中每个打开的文件分配一个文件描述符
- 文件描述符从0开始分配,依次递增
- 文件I/O操作通过文件描述符来完成
标准I/O | 文件描述符 | 文件描述符 | 文件描述字 |
---|---|---|---|
标准输入流 | 0 | STDIN_FILENO | stdin |
标准输出流 | 1 | STDOUT_FILENO | stdout |
标准错误流 | 2 | STDERR_FILENO | stderr |
文件的打开和关闭
open函数用来创建或打开一个文件
#include<fcntl.h>
int open(const char *path,int oflag,…);
- 成功时返回文件描述符;出错时返回EOF
- 打开文件时使用两个参数
- 创建文件时第三个参数指定新文件的权限
- 只能打开设备文件,不能通过open()创建
原形 | int open(const char*pathname,int flags,mode_t mode); | |
---|---|---|
参数 | pathname | 要打开或创建的目标文件(可包括路径名) |
O_RDONLY:只读方式打开文件 | ||
flags | O_WRONLY:只写方式打开文件 | 这三个参数互斥 |
O_RDWR:读写方式打开文件 | ||
O_CREAT:如果该文件不存在,就创建一个新的文件,并用第三的参数为其设置权限 | ||
O_EXCL:如果使用O_CREAT时文件存在,则可返回错误消息,这一参数可测试文件是否存在 | ||
O_NOCTTY:使用本参数时,若文件为终端,那么终端不可以作为调用open()系统调用的那个进程的控制终端 | ||
O_TRUNC:如果文件已经存在,那么打开文件时先删除文件中的原有数据 | ||
O_APPEND:以添加方式打开文件,所有对文件的写操作都在文件末尾进行 | ||
mode | 被打开文件的读取权限,为8进制表示法 |
close函数用来关闭一个打开的文件
#include<unistd.h>
int close(int fd);
- 成功返回0;出错返回EOF
- 程序结束时自动关闭所有打开的文件
- 文件关闭后,文件描述符不再代表文件
测试
//只写方式创建,不存在创建,存在清空
int fd;
if((fd = open("1.txt",O_WRONLY|O_CREAT|O_TRUNC,0666))<0){
perror("open");
return -1;
}
//读写方式创建,不存在创建,存在报错
int fd;
if((df = open("1.txt",O_RDWR|O_CREAT|O_EXCL,0666))<0){
if(errno == EEXIST){
perror("exist error")
}else{
perror("other error");
}
}
读取文件
read函数用来从文件中读取数据:
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t count);
- 成功时返回实际读取的字节数;出错时返回EOF
- 读到文件末尾时返回0
- buf是接收数据的缓冲区
- count应不超过缓冲区大小
测试
从指定的文件(文本文件)中读取内容并统计大小
int main(int argc,char **argv[]){
int fd,n,total = 0;
char buf[64];
if(argc<2){
printf("Usage: %s<file>\n",argv[0]);
return -1;
}
if((fd = open(argv[1],O_RDONLY))<0){
perror("open");
return -1;
}
while((n=read(fd,buf,64))>0){
total+=n;
}
printf("file size is %d\n",total);
return 0
}
写入文件
write 函数用来向文件写入数据:
#include<unistd.h>
ssize_t write(int fd,void *buf,size_t count);
- 成功时返回实际写入的字节数;出错时返回EOF
- buf是发送数据的缓冲区
- count不应超过buf大小
测试
将键盘输入的内容写入文件,直到输入quit
int fd;
char buf[20];
if((fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666))<0){
perror("open");
return -1;
}
while (fgets(buf,20,stdin)!=NULL){
if(strcmp(buf,"quit\n")==0)
break;
write(fd,buf,strlen(buf));
}
定位文件
lseek函数用来定位文件:
#include<unistd.h>
off_t lseek(int fd,off_t offset,int whence);
- 成功时返回当前的文件读写位置;出错时返回EOF
- 参数offset和参数whence同fseek完全一样
测试
利用文件I/O实现文件的复制
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>
#define N 64
int main(int argc,const char **argv){
int fds,fdt,n;
char buf[N];
if(argc<3){
printf("Usage : %s <src_file> <dst_file>\n",argv[0]);
return -1;
}
if((fds = open(argv[1],O_RDONLY))==-1){
fprintf(stderr,"open %s : %s\n",atgv[1],strerror(errno));
return -1;
}
if((fdt = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666))==-1){
fprintf(stderr,"open %s : %s\n",atgv[2],strerror(errno));
return -1;
}
while((n = read(fds,buf,N))>0){
write(fdt,buf,n);
}
close(fds);
close(fdt);
return 0;
}
读取目录
opendir函数用来打开一个目录文件:
#include<dirent.h>
DIR opendir(const charname);
- DIR是用来描述一个打开的目录文件的结构体类型
- 成功时返回目录流指针;出错时返回NULL
readdir函数用来读取目录流中的内容:
#include<dirent.h>
struct dirent *readdir(DIR *dirp);
- struct dirent是用来描述目录流中一个目录项的结构体类型
- 包含成员char d_name[256]
- 成功时返回目录流dirp中下一个目录项;出错或到末尾时返回NULL
closedir 函数用来关闭一个目录文件:
#include<dirent.h>
int closedir(DIR *dirp);
- 成功返回0;失败返回EOF
测试
打印指定的目录下所有文件名称
int main(int argc,char *argv[]){
DIR *dirp;
struct dirent *dp;
if(argc < 2){
printf("Usage : %s <directory>\n",argv[0]);
return -1;
}
if((dirp = opendir(argv[1]))==NULL){
perror("opendir");
return -1;
}
while((dp = readdir(dirp))!=NULL){
printf("%s\n",dp->d_name);
}
closedir(dirp);
return 0;
}
修改文件访问权限
chmod/fchmod函数用来修改文件的访问权限:
#include<sys/stat.h>
int chmod(const char *path,mode_t mode);
int fchmod(int fd,mode_t mode);
- 成功返回0;失败返回EOF
- root和文件所有者能修改文件的访问权限
获取文件属性
stat/lstat/fstat函数用来获取文件属性:
#include<sys/stat.h>
int stat(const char *path,struct stat *buf);
int lstat(const char *path,struct stat *buf);
int fstat(int fd,struct stat *buf);
- 成功返回0;出错时返回EOF
- 如果path是符号链接stat获取的是目标文件的属性;而lstat获取的是链接文件的属性
struct stat是存放文件属性的结构体类型:
类型 | 文件属性 |
---|---|
dev_t st_dev | 文件的设备编号 |
ino_t st_ino | 节点 |
mode_t st_mode | 类型和访问权限 |
nlink_t st_nlink | 连到该文件的硬链接数目,刚建立的文件值为1 |
uid_t st_uid | 所有者id |
uid_t st_gid | 用户组id |
dev_t st_rdev | 设备类型(若此文件为设备文件,则为其设备编号) |
off_t st_size | 文件大小 |
blksize_t st_blksize | 块大小(文件I/O缓冲区的大小) |
blkcnt_t st_blocks | 占用文件区块的个数 |
time_t st_atime | 最后一次访问时间 |
time_t st_mtime | 最后一次修改时间 |
time_t st_ctime | 文件属性最后改变时间 |
mode_t st_mode | 16位short类型 | 含义 |
---|---|---|
S_IFMT | 0170000 | //掩码,过滤st_mode中除文件类型以外的信息 |
S_IFSOCK | 0140000 | //套接字 |
S_IFLNK | 0120000 | //符号链接(软链接) |
S_IFREG | 0100000 | //普通文件 |
S_IFBLK | 0060000 | //块设备 |
S_IFDIR | 0040000 | //目录文件 |
S_IFCHR | 0020000 | //字符设备 |
S_IFIFO | 0010000 | //管道 |
S_ISUID | 0004000 | //设置用户ID |
S_ISGID | 0002000 | //设置组ID |
S_ISVTX | 0001000 | //粘住位 |
S_IRWXU | 00700 | //掩码,过滤st_mode除文件所有者权限以外的信息 |
S_IRUSR | 00400 | //用户读权限 |
S_IWUSR | 00200 | //用户写权限 |
S_IXUSR | 00100 | //用户执行权限 |
S_IRWXG | 00070 | //掩码,过滤st_mode除所属组权限以外的信息 |
S_IRGRP | 00040 | //读权限 |
S_IWGRP | 00020 | //写权限 |
S_IXGRP | 00010 | //执行权限 |
S_IRWXO | 00007 | //掩码,过滤st_mode除其他人权限以外的信息 |
S_IROTH | 00004 | //读权限 |
S_IWOTH | 00002 | //写权限 |
S_IXOTH | 00001 | //执行权限 |
测试
实现ls -l功能
#include<stdio.h>
#include<string.h> //strcmp隐式调用
#include<unistd.h> //readlink隐式调用
#include<stdlib.h>
#include<sys/stat.h>
#include<linux/fs.h>
#include<time.h>
#include<dirent.h>
#include<errno.h>
#include<grp.h>
#include<pwd.h>
void print_size(struct stat *statp){
switch(statp->st_mode & S_IFMT){
case S_IFCHR:
//字符设备
case S_IFBLK:
//块设备
printf("%u,%u",(unsigned)(statp->st_rdev >> 8),(unsigned)(statp->st_rdev & 0xFF));
//st_rdev 若此文件为设备文件,则为其设备的编号
break;
default:
printf("%lu",(unsigned long)statp->st_size);
//st_size 文件字节数(文件大小)
}
}
void print_date(struct stat *statp){
time_t now;
double diff;
char buf[100],*fmt;
if(time(&now)==-1){
printf("??????????");
return;
}
diff=difftime(now,statp->st_mtime);
//st_mtime 最后一次修改时间
/*
%Y : 完整年份 (0000..9999)
%H : 小时(00..23)
%M : 分钟(00..59)
%b : 月份 (Jan..Dec)
%e : 短格式天 ( 1..31)
*/
if(diff<0 || diff>60*60*24*182.5)/*roughly 6 months*/
fmt = "%b%e %Y";
else
fmt = "%b %e %H:%M";
strftime(buf,sizeof(buf),fmt,localtime(&statp->st_mtime));
printf("%s ",buf);
}
void printlong(char *name){
struct stat buf;
struct passwd *user;
struct group *grp;
char linkname[64];
int rt;
rt=lstat(name,&buf);
if(rt==-1){
perror("in printlong:lstat");
return;
}
switch(buf.st_mode & S_IFMT){
case S_IFDIR: printf("d"); break;//目录
case S_IFLNK: printf("l"); break;//软连接
case S_IFREG: printf("-"); break;//普通文件
case S_IFBLK: printf("b"); break;//块设备
case S_IFCHR: printf("v"); break;//字符设备
case S_IFSOCK: printf("s"); break;//套接字
case S_IFIFO: printf("p"); break;//管道
default: printf("?");
}
//OTH其他人,GRP组,USR文件所有者,
putchar((buf.st_mode & S_IRUSR)?'r':'-');
putchar((buf.st_mode & S_IWUSR)?'w':'-');
if(buf.st_mode & S_ISUID)//设置用户ID
putchar((buf.st_mode & S_IXUSR)?'s':'S');
else
putchar((buf.st_mode & S_IXUSR)?'x':'-');
putchar((buf.st_mode & S_IRGRP)?'r':'-');
putchar((buf.st_mode & S_IWGRP)?'w':'-');
if(buf.st_mode & S_ISGID)//设置组ID
putchar((buf.st_mode & S_IXGRP)?'s':'S');
else
putchar((buf.st_mode & S_IXGRP)?'x':'-');
putchar((buf.st_mode & S_IROTH)?'r':'-');
putchar((buf.st_mode & S_IWOTH)?'w':'-');
if(buf.st_mode & S_ISVTX)//S_ISVTX 0001000 设置黏住位
putchar((buf.st_mode & S_IXOTH)?'t':'T');
else
putchar((buf.st_mode & S_IXOTH)?'x':'-');
printf("%lu ",buf.st_nlink);
// st_uid连到该文件的硬链接数目,新建的文件则硬连接数为 1
user=getpwuid(buf.st_uid);
//st_uid用户id
printf("%s ",user->pw_name);
grp=getgrgid(buf.st_gid);
//st_gid组id
printf("%s ",grp->gr_name);
print_size(&buf);
print_date(&buf);
if((buf.st_mode & S_IFMT)==S_IFLNK){
rt=readlink(name,linkname,sizeof(linkname));
linkname[rt]=0;
printf("%s->%s ",name,linkname);
}
else
printf("%s ",name);
puts(" ");
}
int checkfiletype(char *name){
struct stat buf;
int typeflag;
/*S_IFMT是文件类型的位掩码(参见man stat)
直接与mystat.st_mode(mystat.st_mode& S_IFMT)进行逐位AND运算意味着只考虑所涉及的位来确定文件类型(常规文件,套接字,块或字符设备等)*/
lstat(name,&buf);//获取目录的所以信息存储于buf中
switch(buf.st_mode & S_IFMT){//mode st_mode; /*文件类型和存取的权限*/
case S_IFREG: typeflag=1; break;//普通文件
case S_IFDIR: typeflag=2; break;//目录
case S_IFLNK: typeflag=3; break;//符号链接-软连接
case S_IFCHR: typeflag=4; break;//字符设备
case S_IFBLK: typeflag=5; break;//块设备
case S_IFSOCK: typeflag=6; break;//套接字
case S_IFIFO: typeflag=7; break;//管道
default: typeflag=0;
}
return typeflag;
}
int main (int argc,char *argv[]){
int rt=-1;
if(argc!=3){
printf("Usage:%s -l filename\n",argv[0]);//判断输入参数是否为3
exit(1);
}
if(strcmp(argv[1],"-l")!=0){
printf("Usage:%s -l filename\n",argv[0]);//判断第二个参数是否为-l
exit(1);
}
rt=checkfiletype(argv[2]);//判断文件类型
switch(rt){
case 0: printf("unknown file type\n"); exit(1);
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7: printlong(argv[2]); return 0;
}
return 0;
}