Linux系统程序设计:从入门到高级Day03
知识点3【文件IO的操作】
上篇文章中我 帮助大家回忆了C语言中的文件的概述及其操作函数
现在我们来学习一下 Linux中的文件的操作
这里说一下 我下面介绍 都是从头文件 函数(函数功能 参数 返回值)进行介绍,在介绍之后我会举例让大家更好地理解和使用。
常见的文件操作API:open close read write 下面我们依次介绍一下
打开文件 open
由于这里是第一个介绍的,我将教大家这么查询
在Linux 终端 中输入指令 man 2 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)
下面我们分别称为二参open和三参open
函数功能
打开文件,如果文件不存在则可以选择创建
二参open和三参open有什么区别呢?
二参open:
用来打开已经存在的文件
三参open:
用来打开不存在的文件,当文件不存在时,并加以创建文件的行为标志,mode会指定新建文件的权限(所有者权限,同组用户权限,其他用户权限)
问题:这里是选择性创建,在哪里选择呢?
函数参数
pathname
打开文件的路径及文件名
flags
打开文件的行为标志,我带大家在man 2 open中查找
(大家一定要学会查找,Linux中的API背根本背不完)
我来简述一下:
第一段中 加粗的是必选项
第二段中 加粗的是可选项
详细介绍:
必选项
可选项
可选项和必选项 需要按位或(|)起来
上面的问题,选择性创建 就是由行为标志flags 决定的
补充一下flags的使用技巧
在我们写入操作时,常这样写:fd_w = open(“00_shell.sh”,O_WRONLY | O_CREAT,0775);
在我们读操作时, 常这样写:fd_r = open(“01_shell.sh”,O_RDONLY)
因为写入时文件不存在我们需要创建一个新文件
但是读的时候,我们无需创建系文件 因为读取空文件也没有意义
mode
这个我们在Day01 中有介绍这里仅进行简述
mode用八进制表示的
表示都是 0xxx 每个x都是421组合 4:r 2:w 1:x
这三个x的代表请看下图
例如 0775 就是 所有者可读可写可执行 同组用户可读可写可执行 其他用户只读不可写可执行
补充:系统的掩码
功能介绍
系统默认会屏蔽的功能
函数返回值
成功:返回打开的文件描述符
失败:-1
掩码
引入
我们设置的是 0777权限,但是实际的权限确实0775
这就是掩码在起作用
默认屏蔽掉 访客的写权限
查看掩码
umask
掩码底层实现
给定权限 & (~umask)
设置掩码
umask mode
知道就行 不要实操!不要实操!不要实操!
查看各组用户的默认操作权限
umask -S
代码演示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,const char *argv[])
{
int fd;
fd = open("text.txt",O_WRONLY | O_CREAT,0777);
if(fd < 0)
{
perror("open");
return 0;
}
printf("%d\n",fd);
while(1)
{
}
close(fd);
return 0;
}
代码运行结果
注意
一定要在代码运行器件 去查看文件的进程号(原因我们将在进程的课程中讲解)
关闭文件 close
头文件
#include <unistd.h>
函数
int close(int fd);
函数功能
关闭已打开的文件
函数参数
fd:文件描述符,open()的返回值
函数返回值
成功:0
失败:-1
注意:
close工作步骤,先将文件描述符的个数减一,如果文件描述符的个数为0,才会回收文件描述符所占用的内核空间
解释:当我们有两个代码(不是进程),A B,这两个文件都打开同一个文件,那么他们得到的文件描述符是相同的,如果A执行close,这时仅会将文件描述符的个数减一,并不会将文件关闭,直到所有打开 该文件 的代码全部关闭该文件即文件描述符个数为0时后,才会真正意义上的关闭该文件(释放该文件)。
向文件写数据 write
头文件
#include <unistd.h>
函数功能
将指定数目的数据写到文件中
函数
ssize_t write(int fd, const void *buf, size_t count);
函数参数
fd:文件描述符
buf:要写入数据的首地址
count:写入数据的最大长度(字节数)
函数返回值
成功:实际写入的数据个数
失败:-1
代码演示
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,const char *argv[])
{
int fd_w;
fd_w = open("text.txt",O_WRONLY | O_CREAT,0777);
if(fd_w < 0)
{
perror("open");
return 0;
}
//先创建一个数组 并从键盘获取数据数据
char buf[128] = "";
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';//这里注意 '0'和'\0'是不一样的
//将数组的数据写入到磁盘文件中
write(fd_w,buf,sizeof(buf));
//关闭文件
close(fd_w);
return 0;
}
代码运行结果
补充:
如果不关闭文件的后果:
- 文件打开时间过长的时候,会造成系统资源的浪费
- 内核会进行页面刷新,类似于用户态的缓冲区。文件的页存满了会进行页刷新,但是如果不关闭文件,最后会有数据残留在页面缓存部分,导致数据丢失。
从文件读数据 read
头文件
#inclue <unistd.h>
函数功能
头文件个数的数据读取到文件当中
函数
ssize_t read(int fd,void *buf,size_t count)
函数参数
fd:文件描述符
buf:内存首地址
count:最多读取的字节个数
函数返回值
成功:返回读取到的字节个数
失败:-1
代码演示
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,const char *argv[])
{
int fd_w;
fd_w = open("text.txt",O_RDWR | O_CREAT,0777);
if(fd_w < 0)
{
perror("open");
return 0;
}
//先创建一个数组 并从键盘获取数据数据
printf("fd_w的文件描述符:%d\n",fd_w);
char buf[128] = "";
printf("请输入:");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1] = '\0';//这里注意 '0'和'\0'是不一样的
//将数组的数据写入到磁盘文件中
write(fd_w,buf,sizeof(buf));
//读取数据
char buf1[128] = "";
int num1 = 0;
num1 = read(fd_w,buf1,sizeof(buf1));
printf("buf1 = %s\n",buf1);
//关闭文件
close(fd_w);
return 0;
}
代码运行结果
代码分析
这次代码我实在 write函数的基础上写的,只添加了读取数据的部分,和修改和行为标志(O_RDWR)。
这里我想解释的是,这里buf1中数据为空,实际和C语言中文件操作是一样的,需要一个偏移量复位的过程,在C中是流指针复位(如果感到陌生的请看我的Day2 的内容)
如果要buf1成功读取数据 我这里提供两个方法
方法1
先关闭fd_w,然后后以 只读 的行为标志打开fd_r(实现复位操作)
方法2
使用lseek函数
lseek
函数功能
调整文件描述符的读写位置
注意:它的功能是(类比记忆)fseek,ftell,rewind(这里不可以使用)的结合
函数
off_t lseek(int fd, off_t offset, int whence);
函数参数
fd:文件描述符
offset:偏移量(可正可负) 这个偏移量是以whence为基准的偏移量
whence:枚举
SEEK_SET:文件开头。
SEEK_CUR:当前偏移量位置。
SEEK_END:文件末尾。
函数返回值
返回新的文件偏移量(距离文件开头)
代码演示
//符文文件偏移量
lseek(fd_w,0,SEEK_SET);
将上面这部分 加上到读取数据的前面即可
代码运行结果:
主要的文件IO操作就已经讲完了
现在我们来写一个小作业 来给今天的内容进行一个总复习吧。
综合题目练习
题目:代码实现linux中的cp执行 cp 目标文件 目标目录
在写之前,我们先来补充一个知识点:
main函数的参数
int main(int argc,const char *argv[])
{
}
相信大家都经常写这个函数,那大家知道参数argc和argv的含义吗?
argc:命令行参数的个数
argv:指针数组类型,存储所有命令行参数的字符串值
他们两个是是程序与命令行交互的桥梁。
例子说明
./demo hello world 123
argc = 4
argv[0]:./demo
argv[1]:hello
argv[2]:world
argv[3]:123
argv[argc]:NULL
argv[argc] 之后是没有定义的
好了知识点补充完了让我们开始题目吧
代码
代码思路
- 参数个数限制
- 参数的提取及处理
处理是指路径和目标文件名的拼接操作
- 读文件
- 写文件
代码展示
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,const char *argv[])
{
//指令参数个数判断
if(argc != 3)
{
printf("参数数目错误\n");
return 0;
}
//参数的提取和处理
char path[128] = "";
sprintf(path,"%s/%s",argv[2],argv[1]);
//打开文件
int fd_r = open(argv[1],O_RDONLY);
if(fd_r < 0)
{
perror("读文件失败");
return 0;
}
int fd_w = open(path,O_WRONLY | O_CREAT,0775);
if(fd_w < 0)
{
perror("写文件失败");
return 0;
}
//文件读取
int size = 0;
while(1)
{
char arr[128] = "";
size = read(fd_r,arr,sizeof(arr));
write(fd_w,arr,sizeof(arr));
if(size < sizeof(arr))
{
break;
}
}
//关闭文件
close(fd_r);
close(fd_w);
return 0;
}
代码运行结果
结束
代码重在练习,只是脑子懂是远远不够的,跟着博客一起手敲,会有更多收获。
今天的内容就到此结束了,希望对你能够有所帮助,如果你喜欢我的分享,请点赞收藏加关注,谢谢大家!!!