文件操作
文件IO
由操作系统实现并提供给外部应用程序的编程接口。(Application Programming Interface,API)。是应用程序同系统之间数据交互的桥梁。
文件描述符
一个进程启动之后,默认打开三个文件描述符:
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
新打开文件返回文件描述符表中未使用的最小文件描述符, 调用open函数可以打开或创建一个文件, 得到一个文件描述符.
open
打开或新建一个文件;
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数参数:
- pathname参数是要打开或创建的文件名,和fopen一样, pathname既可以是相对路径也可以是绝对路径。
- flags参数有一系列常数值可供选择, 可以同时选择多个常数用按位或运算符连接起来, 所以这些常数的宏定义都以O_开头,表示or。
- O_RDONLY 只读打开
- O_WRONLY 只写打开
- O_RDWR 可读可写打开
- O_APPEND 表示追加。如果文件已有内容, 这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
- O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode, 表示该文件的访问权限。
- O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
- O_TRUNC 如果文件已存在, 将其长度截断为为0字节。
- O_NONBLOCK 对于设备文件, 以O_NONBLOCK方式打开可以做非阻塞I/O(NonblockI/O),非阻塞I/O。
close
关闭文件;
close(int fd);
fd代表文件描述符;
需要说明的是,当一个进程终止时, 内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close, 在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器), 打开的文件描述符一定要记得关闭, 否则随着打开的文件越来越多, 会占用大量文件描述符和系统资源。
read
从打开的设备或文件中读数据
ssize_t read(int fd, void *buf, size_t count);
fd:文件描述符
buf:读上来的数据保存在缓冲区buf中
count:buf缓冲区存放的最大字节数
write
向打开的设备或文件中写数据
ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符
buf:读上来的数据保存在缓冲区buf中
count:buf缓冲区存放的最大字节数
lseek
所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为cfo. cfo通常是一个非负整数, 用于表明文件开始处到文件当前位置的字节数. 读写操作通常开始于 cfo, 并且使 cfo 增大, 增量为读写的字节数. 文件被打开时, cfo 会被初始化为 0, 除非使用了 O_APPEND.
off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符
- 参数 offset 的含义取决于参数 whence。
- 如果 whence 是 SEEK_SET,文件偏移量将设置为 offset。
- 如果 whence 是 SEEK_CUR,文件偏移量将被设置为 cfo 加上 offset,offset 可以为正也可以为负。
- 如果 whence 是 SEEK_END,文件偏移量将被设置为文件长度加上 offset,offset 可以为正也可以为负
Demo代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc,char* args[])
{
int fd = open(args[1],O_RDWR | O_CREAT , 0777);
if(fd<0)
{
perror("open error\n"); //
printf("open error is %s\n",strerror(errno));
return 0;
}
write(fd,"hello,world",strlen("hello,world"));
//移动文件指针
//off_t lseek(int fd, off_t offset, int whence);
lseek(fd,0,SEEK_SET);
char buffer[1024];
memset(buffer,0,sizeof(buffer));
read(fd,buffer,sizeof(buffer));
//printf("%s\n",buffer);
close(fd);
return 0;
}
errno
errno是一个全局变量,当系统调用后若出错后将会对errno进行设置,perror则可以将errno的值打印出来;
perror(errno);
printf("%s\n",strerror(errno)); 这两种办法都可以得到错误信息
dup
复制一个文件描述符
int dup(int oldfd);
oldfd -要复制的文件描述符;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VK0mF6jl-1607298523860)(img/文件操作/0.png)]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc,char* args[])
{
int fd = open(args[1],O_RDWR); //旧文件描述符
if(fd<0)
{
perror("open error");
return -1;
}
int newfd = dup(fd); //新文件描述符
printf("fd = %d newfd = %d\n",fd,newfd);
char buf[1024];
memset(buf,0,sizeof(buf));
write(fd,"hello,world\n",sizeof("hello,world\n")); //使用旧的文件描述符写入文件
//SEEK_SET
lseek(fd,0,SEEK_SET); //移动文件指针到最开始
int n = read(newfd,buf,sizeof(buf)); //使用新的文件描述符读
printf("read over:%d %s\n",n,buf);
close(fd);
close(newfd);
return 0;
}
dup2
如果调用dup2的时候,旧的文件描述符如果打开了一个文件,那么先关闭旧的文件描述符,然后指向新的文件描述符,如果旧的未指向,则全部指向新的文件描述符
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc,char* args[])
{
int fd = open(args[1],O_RDWR);
int fd1 = open(args[2],O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
if(fd1<0)
{
perror("open error");
return -1;
}
dup2(fd,fd1);
printf("fd = %d fd1 = %d\n",fd,fd1);
char buf[1024];
memset(buf,0,sizeof(buf));
write(fd,"hello,world\n",sizeof("hello,world\n"));
//SEEK_SET
lseek(fd1,0,SEEK_SET);
int n = read(fd1,buf,sizeof(buf));
printf("read over:%d %s\n",n,buf);
close(fd);
close(fd1);
return 0;
}
fcntl
函数描述: 改变已经打开的文件的属性
//c函数原型: int fcntl(int fd, int cmd, ... /* arg */ );
-
若cmd为F_DUPFD, 复制文件描述符, 与dup相同
-
若cmd为F_GETFL, 获取文件描述符的flag属性值
-
若cmd为 F_SETFL, 设置文件描述符的flag属性
函数返回值:返回值取决于cmd
如果执行成功:
-
若cmd为F_DUPFD, 返回一个新的文件描述符
-
若cmd为F_GETFL, 返回文件描述符的flags值
-
若cmd为 F_SETFL, 返回0
失败返回-1, 并设置errno值.
//1 复制一个新的文件描述符:
int newfd = fcntl(fd, F_DUPFD, 0);
//2 获取文件的属性标志
int flag = fcntl(fd, F_GETFL, 0)
//3 设置文件状态标志
flag = flag | O_APPEND;
fcntl(fd, F_SETFL, flag)
//4 常用的属性标志
//O_APPEND-----设置文件打开为末尾添加
//O_NONBLOCK-----设置打开的文件描述符为非阻塞
复制
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc,char* args[])
{
int fd = open(args[1],O_RDWR | O_CREAT, 0777);
if(fd<0)
{
perror("open error");
return -1;
}
int newfd = fcntl(fd,F_DUPFD,0);
printf("fd = %d newfd = %d",fd,newfd);
write(fd,"hello,world",sizeof("hello,world"));
lseek(fd,0,SEEK_SET);
char buf[64];
memset(buf,0,sizeof(buf));
int n = read(newfd,buf,sizeof(buf));
printf("newfd = %d read = %s\n",n,buf);
close(fd);
close(newfd);
return 0;
}
修改
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc,char* args[])
{
int fd = open(args[1],O_RDWR);
if(fd<0)
{
perror("open error");
return -1;
}
//获取fd的flag标识,修改flag
int flags = fcntl(fd,F_GETFL,0);
flags = flags | O_APPEND;
fcntl(fd,F_SETFL,flags);
write(fd,"hello,world",sizeof("hello,world"));
close(fd);
return 0;
}
阻塞与非阻塞
read
//read函数读文件是否阻塞
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc,char* args[])
{
int fd = open(args[1],O_RDWR);
if(fd<0)
{
perror("open error\n");
printf("error is %s\n",strerror(errno));
return 0;
}
char buf[1024];
memset(buf,0,sizeof(buf));
int n = read(fd,buf,sizeof(buf));
printf("first %s\n",buf);
memset(buf,0,sizeof(buf));
n = read(fd,buf,sizeof(buf));
printf("second %s\n",buf);
close(fd);
return 0;
}
read读普通文件内容完成后,若再次read,则read函数会立刻返回!
反之如何read去读一个设备文件,则是阻塞的;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc,char* args[])
{
int fd = open("/dev/tty",O_RDWR); //打开一个设备文件,tty为终端
if(fd<0)
{
perror("open error");
return -1;
}
char buf[1024];
memset(buf,0,sizeof(buf));
int n = read(fd,buf,sizeof(buf));
printf("%d %s\n",n,buf);
return 0;
}
文件属性
stat
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */ //文件权限
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */ //用户ID
gid_t st_gid; /* group ID of owner */ //组ID
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */ //最后一次修改时间
time_t st_ctime; /* time of last status change */
};
st_mode
目前,st_mode使用了其低19bit. 0170000 => 1+ 3*5 = 16.
其中,最低的9位(0-8)是权限,9-11是id,12-15是类型。
具体定义如下:
S_IFMT 0170000 bitmask for the file type bitfields
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 fifo
S_ISUID 0004000 set UID bit
S_ISGID 0002000 set GID bit (see below)
S_ISVTX 0001000 sticky bit (see below)
S_IRWXU 00700 mask for file owner permissions
S_IRUSR 00400 owner has read permission
S_IWUSR 00200 owner has write permission
S_IXUSR 00100 owner has execute permission
S_IRWXG 00070 mask for group permissions
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 mask for permissions for others (not in group)
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permisson
S_IXOTH 00001 others have execute permission
//使用宏快速判断文件类型
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc,char* args[])
{
//int stat(const char *path, struct stat *buf);
struct stat st;
stat(args[1],&st);
/*
//获取文件类型
if((st.st_mode & S_IFMT) == S_IFREG)
{
printf("普通文件\n");
}
else if((st.st_mode & S_IFMT) == S_ISDIR)
{
printf("目录文件\n");
}
else if((st.st_mode & S_IFMT) == S_ISLNK)
{
printf("链接文件\n");
}
*/
switch (st.st_mode & S_IFMT) {
case S_IFBLK: printf("block device\n"); break;
case S_IFCHR: printf("character device\n"); break;
case S_IFDIR: printf("directory\n"); break;
case S_IFIFO: printf("FIFO/pipe\n"); break;
case S_IFLNK: printf("symlink\n"); break;
case S_IFREG: printf("regular file\n"); break;
case S_IFSOCK: printf("socket\n"); break;
default: printf("unknown?\n"); break;
}
return 0;
}
lstat
//lstat函数详解
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc,char* args[])
{
//int lstat(const char *path, struct stat *buf);
int fd = open(args[1],O_RDONLY);
struct stat st;
int n = lstat(args[1],&st);
if(n<0)
{
perror("open file");
return -1;
}
printf("%d %d %d \n",st.st_uid,st.st_gid,st.st_size);
}
stat 函数与 lstat 函数的区别: 当一个文件是符号链接时,lstat 函数返回的是该符号链接本身的信息;而 stat 函数返回的是该链接指向文件的信息。
目录
opendir
打开一个目录,打开成功返回指向目录的指针;
DIR *opendir(const char *name);
closedir
关闭目录
int closedir(DIR *dirp);
成功返回0,错误返回-1
readdir
读取目录内容–目录项
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
int readdir(unsigned int fd, struct old_linux_dirent *dirp,unsigned int count);
struct old_linux_dirent {
long d_ino; /* inode number */
off_t d_off; /* offset to this old_linux_dirent */
unsigned short d_reclen; /* length of this d_name */
char d_name[NAME_MAX+1]; /* filename (null-terminated) */
}
demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <errno.h>
int main(int argc,char* args[])
{
//DIR *opendir(const char *name);
DIR *pDir = opendir(args[1]);
if(pDir==NULL)
{
perror("opendir error");
return -1;
}
//struct dirent *readdir(DIR *dirp);
//int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
struct dirent *pDent = NULL;
while((pDent=readdir(pDir))!=NULL)
{
if(strcmp(pDent->d_name,".")==0 || strcmp(pDent->d_name,"..")==0) //去除.文件
{
continue;
}
printf("%s->",pDent->d_name);
switch(pDent->d_type)
{
case DT_REG:
printf("普通文件\n");
break;
case DT_DIR:
printf("目录文件\n");
break;
case DT_LNK:
printf("链接文件\n");
break;
}
}
return 0;
}