Linux文件I/O
文件结构
inode: 记录存储文件的各属性
block: 存储文件内容
创建目录
- 系统分配一个inode和至少一个block
- 该inode记录该目录属性,指向block
- 该block记录该目录下相关联的文件或目录的inode编号和名字
创建文件
- 系统分配至少一个inode和与文件大小相对应数量的一个block
- 该inode记录该目录属性,指向block
如果一个目录中的文件数太多,以至于1个block容纳不下这么多文件时,Linux的文件系统会为该目录分配更多的block
读取文件流程
- 读取目录或文件
- 例读取
/home
下的test.c
- 首先根目录的inode编号固定为0
- 通过根目录的inode编号找到其inode结构体,通过inode结构体找到其block
- 目录的block内容为该目录下的文件inode号与文件名字的表格
- 根据文件
test.c
名字在目录的block找到test.c
对应的inode编号,通过该编号就可以找到test.c
的内容,进而完成文件内容读取
文件的基本操作
概念补充
文件描述符
一个运行中的程序被称为一个进程,他有一些与之对应的文件描述符,文件描述符是一些小的,正整数数值的数,通过他们可以访问打开的文件和设备
- 文件描述符是打开文件进程与文件之间的连接
- 文件描述符是一个正整数的值
- 同时打开几个文件,描述符不同
- 一个文件打开多次,描述符也不同
文件权限
系统调用
open系统调用
- 建立一条到文件或设备的访问路径
- 打开或创建文件
- 执行失败返回 -1
Linux下open()函数打开文件,通过定义 int fd = open()函数,
获取文件描述符,背后关系,从文件指针数组中获得尚未分配的指针,fd即为对应的数组下标,打开文件是通过fd来打开文件,对应数组下标一般是从3开始,前三个依次为stdin,stdout,stderr.
此时,再打开新的文件,获取新的文件描述符,就会扫描该数组,找到未使用的,使用它
接口代码
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int open(const char* pathname,int flags,[mode_t mode]);
参数解释
- pathname: 字符指针,指向文件路径及文件名
- flags: 整数形参,定义以何种方式访问文件
- O_RDONLY: 只读打开文件
- O_WRONLY: 只写打开文件
- O_RDWR: 读写打开文件
- O_CREATE: 按
mode
中给出的访问方式创建文件 - …
- mode: 可选参数,只有
flags为O_CREAT
才生效,表示给文件赋予何种权限- 常用数字代表如:0644 ==>
-rw-r--r--
- 常用数字代表如:0644 ==>
实例代码
//参考下方汇总
write系统调用
- 把缓冲区前n个字节写入与文件描述符
filedes
相关联的文件- 返回值是实际写出的字节数
接口代码
#include <unistd.h>
ssize_t write(int fileds,const void* buffer,size_t n);
参数解释
- fileds: 文件描述符
- buffer: 缓冲区
- n: 从缓冲区写入到文件的字节数
实例代码
#include <unistd.h>
#include <stdlib.h>
int main()
{
if((write(1,"Here is come data\n",18)) != 18)
write(2,"A write error has occurred on file description 1\n",46);
exit(0);
}
read系统调用
- 从与文件描述符fildes相关联的文件里读入nbytes个字节的数据,并把他们放到buffer数据区中
- 返回值是实际写入的字节数
- 执行失败返回 -1
接口代码
#include <unistd.h>
ssize_t read(int filedes,void* buffer,size_t nbytes);
参数解释
- filedes: 之前open或create调用返回的文件描述符
- buffer: 指向数组或结构的指针
- nbytes: 从文件中读取的字节数
实例代码
#include <unistd.h>
#include <stdlib.h>
int main()
{
char buffer[128];
int nread;
nread = read(0,buffer,128);
if(nread == -1)
write(2,"A read error has occurred\n",26);
if((write(1,buffer,nread)) != read)
write(2,"A write error has occurred\n",27);
exit(0);
}
create系统调用
- 创建并打开文件
接口代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char* pathname,mode_t mode);
参数解释
- pathname: 文件路径名
- mode: 为赋予创建文件的访问权限
实例代码
//参考下方汇总
close系统调用
- 终止一个文件描述符与文件的关联
- 文件描述符可以被重新使用
- 一个运行的程序一次性能打开的文件数量是有限的
- 执行成功返回 0
接口代码
#include <unistd.h>
int close(int filedes);
参数解释
- filedes: 文件描述符
实例代码
//参考下方汇总
综合应用
将一个文件内容复制到另一个文件内
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
int in,out;
// 源文件路径
char* inPath = "test.txt";
// 目标文件路径
char* outPath = "out.txt";
// 定义缓冲区
char buffer[1024] ={0};
// 以只读方式打开文件
in = open(inPath,O_RDONLY);
// 以rw-r--r--打开文件
out = open(outPath,0644);
// 输出对应文件描述符
printf("in = %d,out = %d\n",in,out);
if(in == -1) exit(1);
// 如果目标文件不存在,就创建
if(out == -1) out = creat(outPath,0644);
// 每次成功读取字节数
ssize_t nread;
while((nread = read(in,buffer,sizeof(buffer))) > 0)
{
// 将缓冲区字节数写入目标文件
write(out,buffer,nread);
printf("nread = %ld\n",nread);
printf("%s\n",buffer);
}
close(in);
close(out);
exit(0);
}
文件状态信息
stat结构体成员
- st_mode 文件权限和文件类型信息
- 掩码:
- S_IFMT 文件类型
- S_IRWXU 所属者的读/写/执行权限
- S_IRWXG 所属组的读/写/执行权限
- S_IRWXO 其他用户的读/写/执行权限
- S_ISBLK 测试是否是特殊的块设备文件
- S_ISCHR 测试是否是特殊的字符设备文件
- S_ISDIR 测试是否是目录
- S_ISFIFO 测试是否是FIFO设备
- S_ISREG 测试是否是普通文件
- S_ISLNK 测试是否是符号链接文件
- st_ino 与该文件关联的inode
- st_dev 保存文件的设备
- st_uid 文件属性的UID号
- st_gid 文件属性的GID号
- st_atime 文件上一次被访问的时间
- st_ctime 文件的权限、所有者、组或内容上一次改变的时间
- st_mtime 文件的内容上一次被修改的时间
- st_nlink 该文件上硬连接的个数
fstat系统调用
返回一个打开文件描述符关联着的文件状态信息,这些信息被写到
stat
结构里
接口代码
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int fstat(int fildes, struct stat* buf);
参数解释
- filedes: 文件描述符
- stat: 文件状态信息结构体
stat系统调用
- 使用文件名查看文件状态信息
- 当文件是符号链接时,返回该连接指向的文件的信息
接口代码
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int stat(const char* path, struct stat* buf);
lstat系统调用
- 使用文件名查看文件状态信息
- 当文件是符号链接时,返回符号连接本身
接口代码
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int lstat(const char* path, struct stat* buf);
lseek系统调用
- 改变已打开文件中指针位置
接口代码
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fileds, off_t offset,int start_flag);
参数解释
- filedes: 参数为文件描述符
- offset: 参数为表示新位置相对起始位置的字节数
- start_flag:
- offset从文件的起始文件开始算,通常值为0
- offset相对文件读写的当前位置而言,通常值为1
- offset相对文件尾而言,通常值为2
perror函数
- 用来打印错误信息
接口代码
#include <stdio.h>
void perror(const char *s);
参数解释
- 如果s不为空,错误信息会先增加字符串s的内容,再打印错误信息
实例代码
#include <stdio.h>
int sample
{
int fd;
fd = open("file",O_RDONLY);
if(fd == -1)
{
perror("Cannot open file");
return;
}
}
//运行结果
Cannot open file: No such file or directory
Cannot open file: Interrupted system call
chmod系统调用
- 修改文件或目录的访问权限
- 改变文件或目录的所有者或组
接口代码
**#include <sys/stat.h>**
// 修改文件或目录的访问权限
int chmod(const char *path, mode_t mode);
// 改变文件或目录的所有者或组
int chown(const char *path, uid_t owner, gid_t group);
参数解释
- path: 指定被修改权限的文件
- mode: 修改的权限设置
- owner: 用户id
- group: 组id
实例代码
#include <unistd.h>
#include <sys/stat.h>
int main()
{
chmod("abc",04764);
chmod("bcd",S_ISUID|S_IRWXU|S_IRGRP|S_IWGRP|S_IROTH);
chmod("abc",1000,1000);
return 0;
}
文件链接
unlike函数
- 删除链接
- 对应链接数减一,如果为0,则删除
- 是删除该文件所在目录的一条目录数据项,即删除目录文件中的一个文件名及其inode号
接口代码
#include <unistd.h>
int unlink(const char *pathname);
link函数
- 创建链接(硬链接)
- ln a b : a,b共享同一个inode,存储信息,可以看作同一个文件的两个名字
接口代码
#include <unistd.h>
int link(const char *path1, const char *path2);
symlink函数
- 创建符号链接(软链接)
- ln -s a b : b相当于a的快捷方式,这是两个文件
接口代码
#include <unistd.h>
int symlink(const char *path1, const char *path2);
目录操作
opendir函数
- 打开目录
- 返回指向目录流的指针DIR
接口代码
#include <sys/types.h>
#include <dirent.h>
DIR* opendir(const char* dirname);
readdir函数
- 返回一个指向结构的指针
- dirent结构体
struct dirent { long d_ino;// 索引节点inode号 off_t d_off;// 在目录文件中的偏移 unsigned short d_reclen;// 文件名长 unsigned char d_type;// 文件类型 char d_name [NAME_MAX+1];// 文件名 }
接口代码
#include <sys/types.h>
#include <dirent.h>
struct dirent* readdir(DIR* drip);
参数解释
- drip: 目录流指针
实例代码
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main()
{
DIR* dir_ptr;
struct dirent* dirrntp;
// 打开目录
if((dir_ptr == opendir("/home")) == NULL)
perror("can not open /home");
// 循环读出该目录下每一项
while((direntp == readdir(dir_ptr)) != NULL)
printf("%s\n",direntp->d_name);
close(dir_ptr);
return 0;
}
telldir函数
- 返回目录流当前位置
接口代码
#include <sys/types.h>
#include <dirent.h>
long int telldir(DIR* dirp);
参数解释
- drip: 目录流指针
实例代码
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main()
{
DIR* dir_ptr;
struct dirent* dirrntp;
int dir_loc;
// 打开目录
if((dir_ptr == opendir("/home")) == NULL)
perror("can not open /home");
// 循环读出该目录下每一项
while((direntp == readdir(dir_ptr)) != NULL)
{
printf("%s\n",direntp->d_name);
// 获取当前文件流位置
dir_loc = telldir(dir_ptr);
printf("%d\n",dir_loc);
}
close(dir_ptr);
return 0;
}
seekdir函数
- 设置目录流dirp的目录指针
接口代码
#include <sys/types.h>
#include <dirent.h>
void seekdir(DIR* dirp,long int loc);
参数解释
- drip: 目录流指针
- loc: 用来设置指针位置,通过telldir调用获得
closedir函数
- 关闭目录流并释放与之相关联的资源
接口代码
#incldue <sys/types.h>
#include <dirent.h>
int closedir(DIR* dirp);
综合实例
求文件目录下的所有文件
知识点:
- “.”: 表示当前目录
- “…”: 表示父目录
- 遍历时应当排除
strcmp(char* str1,char* str2)
: 比较两个字符串是否相等
strcpy(char* str1,char* str2)
: 将str2赋值给str1
strcat(char* str1,char* str2)
: 将str2拼接到str1后
// Linux下输入文件
$ gcc -o findDir findDir.c
$ ./findDir ../
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <locale.h>
#include <stdint.h>
#include <string.h>
#define LINES 200
#define NAMELEN 1024
char fileDir[LINES][NAMELEN];
int front = 0;// 队头指针
int rear = 0;// 队尾指针
char* out = "./ans.txt";// 输出文件
FILE* fq;
void insert(char* str)
{
strcpy(fileDir[rear++],str);
if(rear % LINES == 0)rear = 0;
}
char* pop()
{
char* str = fileDir[front];
front++;
if(front % LINES == 0)front = 0;
return str;
}
int empty()
{
return rear == front ? 1 : 0;
}
void findDir(char* fileNamePath)
{
DIR* dir;
struct dirent* dirDetails;
struct stat buff;
if((dir = opendir(fileNamePath)) == NULL)
{
printf("%s文件目录不存在\n",fileNamePath);
return;
}
// 队列开始,循环判断每一项
while((dirDetails = readdir(dir)) != NULL)
{
char* str1 = ".";
char* str2 = "..";
int ren[2];
ren[0] = strcmp(dirDetails->d_name,str1);
ren[1] = strcmp(dirDetails->d_name,str2);
// 过滤当前目录.和父目录..
if(ren[0] == 0 || ren[1] == 0 )continue;
// 过滤隐藏文件
if(dirDetails->d_name[0] == '.')continue;
char fullPath[1024] = {0};
strcpy(fullPath,fileNamePath);
int len = strlen(fullPath);
// 这一步???
if(fullPath[len-1] != '/')
{
fullPath[len++] = '/';
fullPath[len] = 0;
}
// 拼接得到完整目录文件
strcat(fullPath,dirDetails->d_name);
if(stat(fullPath,&buff) == -1)
{
printf("00000000000000,fullPath = %s\n",fullPath);
continue;
}
else
{
if(S_ISDIR(buff.st_mode))
{
strcat(fullPath,"/");// 构建成目录
insert(fullPath);// 加入队列
}
else fprintf(fq,"%s\n",fullPath);// 输出文件内容
}
}
closedir(dir);// 关闭目录流
if(!empty())
{
char* dirPath = pop();
findDir(dirPath);
}
}
int main(int argc,char* argv[])
{
fq = fopen(out,"w");
if(argc != 2)
{
printf("error卒\n");
exit(-1);
}
findDir(argv[1]);
fclose(fq);
printf("结束\n");
exit(0);
}
文件入口getopt函数
函数原型
#include <unistd.h>
int getopt(int argc, char * const argv[], const char *optstring);
参数
argc
: 命令行参数的个数,通常由main
函数传递给getopt
。argv
: 命令行参数的数组,通常由main
函数传递给getopt
。optstring
: 一个字符串,定义了有效选项及其参数的格式。每个字符代表一个选项:- 如果字符后面跟有冒号(如
a:
),则表示这个选项需要一个参数。 - 如果字符后面没有冒号(如
b
),则表示这个选项不需要参数。 - 如果
optstring
中包含?
,getopt
会在遇到无效选项时返回?
。
- 如果字符后面跟有冒号(如
返回值
- 选项字符:
getopt
会返回当前处理的选项字符。如果选项需要一个参数,optarg
会指向这个参数的字符串。 -1
: 当没有更多选项可以处理时,getopt
返回-1
。
全局变量
optarg
: 指向当前选项的参数。如果当前选项需要一个参数(如-o value
中的value
),optarg
指向该参数的字符串。如果选项不需要参数,则optarg
为NULL
。optind
: 在解析选项后,指向argv
中下一个未处理的参数的位置。它可以用来访问剩余的命令行参数。opterr
: 用于控制getopt
是否输出错误消息。如果opterr
被设置为 0,getopt
将不会输出错误消息。默认情况下,它的值是 1。optopt
: 当遇到无效的选项时,optopt
被设置为无效选项字符。它可以用于自定义错误消息。
示例代码
下面是一个使用 getopt
的简单示例:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int opt;
while ((opt = getopt(argc, argv, "a:b::c")) != -1) {
switch (opt) {
case 'a':
printf("Option -a with value %s\n", optarg);
break;
case 'b':
printf("Option -b with value %s\n", optarg ? optarg : "none");
break;
case 'c':
printf("Option -c\n");
break;
case '?':
if (optopt == 'b') {
printf("Option -%c requires an argument.\n", optopt);
} else {
printf("Unknown option `-%c'.\n", optopt);
}
return 1;
default:
abort();
}
}
// Print remaining arguments
for (int i = optind; i < argc; i++) {
printf("Remaining argument: %s\n", argv[i]);
}
return 0;
}
解释
opt = getopt(argc, argv, "a:b::c")
:- 处理选项
-a
,该选项需要一个参数。 - 处理选项
-b
,该选项可以有一个可选参数(冒号后面有两个冒号表示可选)。 - 处理选项
-c
,该选项不需要参数。
- 处理选项
switch (opt)
:case 'a':
处理-a
选项及其参数,打印选项和参数。case 'b':
处理-b
选项及其参数,如果参数缺失,使用默认值"none"
。case 'c':
处理-c
选项,不需要参数。case '?':
处理无效选项或缺少参数的情况。
for (int i = optind; i < argc; i++)
:- 遍历并打印所有剩余的命令行参数。