目录
文件IO顾名思义就是linux系统中专门操作文件的函数
一、文件描述符
文件描述符是一个非负整数,是Linux内核用来标识进程打开文件的索引。可以把它理解为文件的"代号"或"编号"
特点:
每个进程都有自己的文件描述符表
文件描述符从0开始分配
0、1、2是系统预留的标准描述符
新打开的文件从3开始分配
0 STDIN_FILENO 标准输入键盘1 STDOUT_FILENO 标准输出屏幕
2 STDERR_FILENO 标准错误屏幕
二、open函数
int open(const char *pathname, int flags, mode_t mode);
pathname:文件路径(字符串)
flags:打开方式标志
mode:权限模式(八进制,当创建文件时使用)
返回值:成功返回文件描述符(≥0),失败返回-1
flag参数:
O_RDONLY 只读打开 只能读取,不能写入
O_WRONLY 只写打开 只能写入,不能读取
O_RDWR 读写打开 既可读又可写
O_CREAT 创建文件 文件不存在时创建
O_TRUNC 截断文件 打开时清空文件内容
O_APPEND 追加模式 写入时追加到文件末尾
示例:
#include <stdio.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
if(argc != 2)
{
printf("%s <filename>\n", argv[0]);
return -1;
}
// 打开文件:读写模式 + 创建(如果不存在)
// 0777:权限设置为rwxrwxrwx //0666则为rw-rw-rw-
int fd = open(argv[1], O_RDWR|O_CREAT, 0777);
if(fd < 0)
{
perror("open fail");
return -1;
}
printf("success\n");
return 0;
}
三、read/write函数
ssize_t read(int fd, void *buf, size_t count); //读
fd:文件描述符
buf:缓冲区指针(存放读取的数据)
count:要读取的字节数
返回值:实际读取的字节数,0表示文件结束,-1表示错误
ssize_t write(int fd, const void *buf, size_t count); //写fd:文件描述符(1表示标准输出)
buf:要写入的数据缓冲区
count:要写入的字节数
返回值:实际写入的字节数,-1表示错误
示例:实现cp功能:
cs
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
printf("%s <filename> <filename>\n",argv[0]);
return -1;
}
int fd = open(argv[1],O_RDONLY|O_CREAT,0666);
int fd2 = open(argv[2],O_WRONLY|O_CREAT,0666);
if(fd < 0 || fd2 <0)
{
perror("open fail");
return -1;
}
char buf[1024];
int ret =0;
while((ret = read(fd,buf,sizeof(buf)))!=0)
{
//buf[ret] = '\0'; // 添加字符串结束符
//printf("ret = %d buf = %s\n", ret, buf);
write(fd2,buf,ret);
}
return 0;
}
四、lseek函数
off_t lseek(int fd, off_t offset, int whence); //移动文件读写位置
fd:文件描述符
offset:偏移量(正数向后,负数向前)
whence:起始位置参考点
返回值:新的文件偏移量,-1表示错误
whence参数:
SEEK_SET文件开头从文件开始位置计算偏移
SEEK_CUR当前位置从当前读写位置计算偏移
SEEK_END文件末尾从文件结束位置计算偏移
示例:创建空洞文件hole.c
cs
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
if(argc != 3)
{
printf("%s <源文件> <空洞文件>\n", argv[0]);
return -1;
}
int fd = open(argv[1], O_RDONLY|O_CREAT, 0666);
int fd2 = open(argv[2], O_WRONLY|O_CREAT, 0666);
if(fd < 0 || fd2 < 0)
{
perror("open fail");
return -1;
}
// 获取源文件大小
off_t len = lseek(fd, 0, SEEK_END);
// 在新文件中跳到(len-1)位置
lseek(fd2, len-1, SEEK_SET);
// 写入一个字节,创建空洞文件
write(fd2, "", 1);
close(fd);
close(fd2);
return 0;
}
五、close函数
int close(int fd);
释放系统资源(文件描述符是有限的)
确保数据写入磁盘
避免资源泄漏
六、stat函数
int stat(const char *pathname, struct stat *statbuf);
pathname:文件路径
statbuf:stat结构体指针(存放文件属性)
返回值:成功返回0,失败返回-1
简单示例:获取文件大小
cs
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
struct stat st;
if(stat(argv[1], &st) < 0)
{
perror("fail");
return -1;
}
printf("size = %ld\n", st.st_size);
return 0;
}
具体结构体成员使用命令Man 2 stat来查看手册
练习:实现stat功能:
重点理解st_mode的用法
cs
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
int main(int argc, const char *argv[])
{
if (argc != 2)
{
printf("Usage: %s <filename>\n",argv[0]);
return -1;
}
struct stat st;
if (stat(argv[1],&st) < 0)
{
perror("stat fail");
return -1;
}
printf(" File: %s \n",argv[1]);
printf(" Size: %ld\t Blocks: %ld\tIO Block: %ld ",st.st_size,st.st_blocks,st.st_blksize);
switch (st.st_mode & S_IFMT)
{
case S_IFSOCK:
puts(" socket file");
break;
case S_IFLNK:
puts(" symlink file");
break;
case S_IFREG:
puts(" regular file");
break;
case S_IFBLK:
puts(" block file");
break;
case S_IFDIR:
puts(" directory file");
break;
case S_IFCHR:
puts(" character file");
break;
case S_IFIFO:
puts(" pipe file");
break;
default:
puts(" unkonwn file");
break;
}
printf("Device: %ld\t Inode: %ld Links: %ld\n",st.st_dev,st.st_ino,st.st_nlink);
printf("Access: (%#o/",st.st_mode&(S_IRWXU|S_IRWXG|S_IRWXO));
switch (st.st_mode & S_IFMT)
{
case S_IFSOCK:
putchar('s');
break;
case S_IFLNK:
putchar('l');
break;
case S_IFREG:
putchar('-');
break;
case S_IFBLK:
putchar('b');
break;
case S_IFDIR:
putchar('d');
break;
case S_IFCHR:
putchar('c');
break;
case S_IFIFO:
putchar('p');
break;
default:
putchar('?');
break;
}
//所有者
st.st_mode&S_IRUSR?putchar('r'):putchar('-');
st.st_mode&S_IWUSR?putchar('w'):putchar('-');
st.st_mode&S_IXUSR?putchar('x'):putchar('-');
//所属组
st.st_mode&S_IRGRP?putchar('r'):putchar('-');
st.st_mode&S_IWGRP?putchar('w'):putchar('-');
st.st_mode&S_IXGRP?putchar('x'):putchar('-');
//其他人
st.st_mode&S_IROTH?putchar('r'):putchar('-');
st.st_mode&S_IWOTH?putchar('w'):putchar('-');
st.st_mode&S_IXOTH?putchar('x'):putchar('-');
printf(") Uid:(%d/%s) Gid:(%d/%s)\n",st.st_uid,getpwuid(st.st_uid)->pw_name,st.st_gid,getgrgid(st.st_gid)->gr_name);
struct tm *ptm = localtime(&st.st_atime);
printf("Access: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
ptm = localtime(&st.st_mtime);
printf("Modify: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
ptm = localtime(&st.st_ctime);
printf("Change: %04d-%02d-%02d %02d:%02d:%02d\n",ptm->tm_year+1900,ptm->tm_mon+1,ptm->tm_mday,
ptm->tm_hour,ptm->tm_min,ptm->tm_sec);
return 0;
}
七、目录操作
DIR *opendir(const char *name);//打开
struct dirent *readdir(DIR *dirp);//读取(注意:目录操作没有写)
int closedir(DIR *dirp);//关闭
示例:计算文件的里面的目录和文件数量
cs
#include <stdio.h>
#include <dirent.h>
int main(int argc, const char *argv[])
{
if(argc != 2)
{
printf("fail:%s <目录名>\n", argv[0]);
return -1;
}
// 打开目录
DIR *dp = opendir(argv[1]);
if (dp == NULL)
{
perror("fail");
return -1;
}
struct dirent *pd = NULL;
int cntdir = 0; // 目录计数
int cntfile = 0; // 文件计数
while(1)
{
// 读取目录项
pd = readdir(dp);
if (pd == NULL) // 读取完毕
{
break;
}
// 跳过隐藏文件(.开头)
if(pd->d_name[0] != '.')
{
// 根据类型计数
if(pd->d_type == DT_DIR)
{
++cntdir;
}
else if(pd->d_type == DT_REG)
{
++cntfile;
}
}
}
printf("目录有%d个,文件有%d个\n", cntdir, cntfile);
closedir(dp);
return 0;
}
注意:这里的.是表示当前目录, ..是表示上一级目录, 使用ls -a 可以查看隐藏文件
八、FILE指针与文件描述符的转换
首先这两者的区别是:

文件IO每次读写都会进入内核,而标准IO使用用户空间缓冲区(通常8KB或更大)满了再进入内核或者强制fflush()
转换函数:
FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);
示例:相互转换
cs
#include <stdio.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
if(argc != 2)
{
printf("%s <filename>\n", argv[0]);
return -1;
}
// 使用open打开文件,获得文件描述符
int fd = open(argv[1], O_RDWR|O_CREAT, 0777);
if(fd < 0)
{
perror("open fail");
return -1;
}
printf("fd=%d\n", fd);
// 文件描述符 → FILE指针
FILE *fp = fdopen(fd, "r"); // 模式必须与fd打开模式一致
if(fp == NULL)
{
perror("fail");
return -1;
}
// FILE指针 → 文件描述符
fd = fileno(fp);
printf("fileno = %d\n", fd);
close(fd);
fclose(fp);
return 0;
}
注意:混用IO的话,不管是先用的是文件IO还是标准IO都是先关文件IO再关标准IO
九、软链接和硬链接
Linux系统中一个文件由目录项(struct dirent)、inode表和数据块组成
目录项 :包括文件名 和inode 节点号
Inode 表:包含文件的一些信息。
例如,inode号,文件类型,文件大小等待。
数据块:文件具体内容存放的地方。
1.硬链接
本质:是给我们的文件取别名。
与源文件直接关联。修改一个,另一个同时被修改 。删除一个另一个不会有影响。
Linux系统中规定,只有在文件的打开次数为0(即文件这个时候没有打开),并且文件的硬连接数为0,此时文件才会被删除特点:
(1)不占用硬盘空间
(2)不能对目录操作
(3)不能跨文件系统
命令 :ln 源文件名 硬链接文件名
2.软链接
本质:类似于我们 windows 的快捷方式,用来记录我们目标的路径
特点:
(1)占用磁盘空间
(2)可以对目录操作
(3)可以跨文件系统
命令:ln -s 源文件 软链接文件
两者区别:
硬链接原文件和新文件的inode编号一致。而软链接不一样。
对原文件删除,会导致软链接不可用,因为软连接存储是路径,而硬链接不受影响