【Linux】文件基础 IO 操作

库函数 IO

打开文件 fopen

fopen 返回一个文件的操作句柄,有了句柄才能对指定文件进行操作
FILE fopen(char pathname,char* mode);

mode—文件操作方式:
(1)r----只读打开(文件不存在报错)
(2)w----只写打开(文件不存在会创建,存在则截断长度为 0 ,丢弃原内容)
(3)a----追加写打开(文件不存在创建,存在则写入数据总是写入文件末尾)
(4)r+ -----读写打开(文件不存在报错)
(5)w+ ----读写打开(文件不存在创建,丢弃原内容)
(6)a+ ---- 读写打开(不存在创建,数据写入末尾)
(7)b ---- 以二进制形式打开(默认不使用 b ,则使用文本方式打开-----对特殊字符处理会有不同)

以文本形式打开,有可能会造成读到的数据与文件实际的数据有差别

在这里插入图片描述

向文件写入数据 fwrite

fwrite
size_t fwrite(char* data,int bsize,int nmem,FILE*fp);
(1)bsize:文件块个数
(2)nmem:文件块大小

在这里插入图片描述

从文件读取数据 fread

fread
size_t fread(char* buf,int bsize,int nmem,FILE* fp);

在这里插入图片描述

关闭文件 fclose

fclose
int fclose(FILE* fp);

在这里插入图片描述

跳转当前读写位置 fseek

fseek
int fseek(FILE* fp,int offset,int whence);
(1)offset:偏移地址
(2)whence:文件起始地址
从 whence 位置进行偏移,whence 可能取值:SEEK_SET(文件起始地址)、SEEK_END(文件末尾)、SEEK_CUR(指定起始位置)

在这里插入图片描述

练习

   1 #include<stdio.h>                                                         
    2 #include<string.h>
    3 
    4 int main()
    5 {
    6   FILE* fp=fopen("./test.txt","w+");   //w+ 读写方式打开
    7   if(NULL==fp){
    8     perror("fopen error");
    9     return -1;
   10    }
   11 
W> 12   char *data="天黑了!\n";
   13   //fwrite(数据地址,块大小,块个数,句柄)
   14   size_t ret=fwrite(data,1,strlen(data),fp);   //返回实际写入的完整块个数
   15 
   16   //其实就是写入的字节长度,因为块大小为 1
   17 
   18   if(ret!=strlen(data)){
   19     perror("fwrite error!");
   20     fclose(fp);
   21     return -1;
   22   }
   23 
   24 
   25   fseek(fp,0,SEEK_SET);  //从文件起始位置开始跳转
   26 
   27 
   28   char buf[1024]={0};
   29   ret=fread(buf,1,1023,fp);  //块大小 1,块个数=想要读取的长度,这样返回值      就是实际读取到的字节长度
   30   if(ret==0){
   31    if(feof(fp))    //判断是否读到文件末尾
   32       printf("读取到了文件末尾,end of file");
   33     if(ferror(fp))    //判断上一次对 fp 的操作是否出错
   34     {
   35       perror("fread error");
   36       fclose(fp);
   37       return -1;
   38     }   
   39   }
   40   
   41   printf("%s\n",buf);
   42   fclose(fp);
   43 
   44   return 0;                                                               
   45 }

运行结果:

在这里插入图片描述

在这里插入图片描述

以上都是库函数,库函数是对系统调用接口的封装

系统调用接口:
open、write、read、close、lseek

系统调用接口

open、write、read、lseek、close

open

int open(char* pathname,int flag);
int open(char* pathname,int flag,int mode);
(1) pathname:要打开的文件路径

(2) flag:文件的打开方式
必选其一:
O_RDONLY ----00 可读
O_WRONLY ----01 可写
O_RDWR ----- 02 可读可写
可选项:
O_CREAT -----文件不存在会创建
O_TRUNC ----- 截断文件原有内容,丢弃原内容
O_APPEND ---- 追加写

w+:可读可写,文件不存在则会创建,文件已存在则截断内容为0 ======= O_RDWR | O_CREAT | O_TRUNC

a+:可读追加写 ,文件不存在则会创建 ======= O_RDWR | 0=CREAT | O_APPED

(3) mode:当 O_CREAT 被使用时,就一定要设置 mode ,用于设定文件访问权限 0664

0664 --------- 前边的 0 不能省略,否则会涉及特殊权限位的设置

给定权限会受到 umask 影响:

实际权限=给定权限 & (~umask),默认 umask=0002

mode_t umask(mode_t mask); 将当前调用的进程掩码设置位 mask ,并不会影响外部的 umask

返回值:打开成功则返回一个文件描述符(非负整数)作为文件操作句柄;失败返回 -1

write

ssize_t write(int fd,char* buf,size_t len);
(1) fd: open 打开文件时返回的操作句柄-文件描述符
(2) buf: 要写入的数据所在空间首地址
(3) len: 要写入数据的长度(字节为单位)
返回值:成功返回实际写入数据长度,失败返回 -1

read

ssize_t read(int fd,char* buf,size_t len);
(1) fd : open打开文件时的操作句柄
(2) buf:一块空间首地址,用于存放读取到的数据
(3) len:读取长度,不能大于buf空间的大小,避免越界
返回值:成功返回实际读取到的数据长度(字节为单位),失败返回 -1,0表示读到文件末尾

lseek

off_t lseek(lint fd,off_t offset,int whence);
(1)fd:文件描述符
(2)offset:偏移量
(3)whence:偏移的起始位置
SEEK_SET; SEEK_CUR; SEEK_END
返回值:当前跳转后,读写位置相当于文件起始位置位置的偏移量(接口有一种另类用法,跳转到文件末尾,通过返回值确定文件大小)

close

int close(int fd);
fd:文件描述符

说明:

size_t :无符号整型
ssize_t :有符号整型
off_t:整型

使用练习:

 1 #include<stdio.h>
  2 #include<string.h>
  3 #include<sys/stat.h>
  4 #include<fcntl.h>
  5 #include<unistd.h>                                                                       
  6 
  7 int main()
  8 {
  9   umask(0);      //采用默认设置的权限值,只在当前文件内有效
 10   int fp=open("./tmp.txt",O_RDWR|O_CREAT|O_TRUNC,0777);
 11   if(fp<0){
 12     perror("open error");
 13     return -1;
 14   }
 15 
 16   //打开成功
 17 
 18   const char *str="hello world!\n";
 19   ssize_t ret=write(fp,str,strlen(str));
 20   if(ret<0){
 21     perror("write error");
 22     close(fp);
 23     return -1;
 24   }
 25 
 26   //写入数据之后,文件标识到达文件末尾
 27   lseek(fp,0,SEEK_SET); //起始位置开始
 28 
 29 
 30   //写入成功,读取数据
 31   char buf[1024];
  32   ret=read(fp,buf,strlen(str));
 33 
 34   if(ret<0){
 35       perror("read error");
 36       close(fp);
 37       return -1;
 38   }
 39   printf("%s",buf);
 40   close(fp);
 41   return 0;
 42 }               

运行结果:
在这里插入图片描述
在这里插入图片描述

库函数是对系统调用接口的一个封装,一般来说对于系统调用接口来说是没有缓冲区的,而库函数封装了缓冲区以及系统调用接口(还有文件描述符)--------- 类似于前边所说的 exit 与 _exit

重定向

将原本要写入 A 位置的数据不写入 A ,而是写入 B

ls > a.txt 标准输出重定向,将原本要写入标准输出打印的数据写入 a.txt 文件

>:清空重定向,清空原来内容
>>:追加重定向,追加到原内容末尾

在这里插入图片描述


./main > /dev/null 2>&1:

将标准输出与标准错误都重定向到 /dev/null 文件中
(1) > /dev/null :将标准输出重定向到 /dev/null 文件
(2) 2>&1 : &1表示标准输出,相当于将标准错误重定向到标准输出 ,2 是标准错误

在这里插入图片描述

./main 2>&1 /dev/null:
将标准错误打印到标准输出,再将标准输出写入到 /dev/null 文件当中

在这里插入图片描述

文件描述符

在这里插入图片描述

0-标准输入
1-标准输出
2-标准错误

文件描述符是按照最小未使用原则进行分配的,因此应该从 3 开始进行分配:

在这里插入图片描述

#include<stdio.h>
#include<fcntl.h>          //open 头文件
#include<unistd.h>

int main()
{
	int fd=open("tmp.txt",O_RDWR);
	if(fd<0){
		perror("open error");
		return -1;
	}

//打开文件成功
printf("fd = %d\n",fd);    
//当再打开文件之前并未关闭标准输入、标准输出、标准错误描述符时,会按照最小未使用原则进行,则此时打开的文件描述符为 3
close(fd);
return 0;
}

(1)close(0); 关闭标准输入

当关闭了 0 号描述符之后,打开一个新文件,会默认按最小未使用原则,新文件的描述符就会成为 0:

在这里插入图片描述

当在打开文件前关闭了标准输出,则printf 不会打印出来内容:

(2)close(1) ; 关闭标准输出

printf 是向 stdout 写入数据,stdout 本质是封装了 1 号文件描述符信息,printf 打印信息,实际是将数据信息写入了 stdout 缓冲区,但是 close 关闭文件时会直接将缓冲区的信息直接释放。

在这里插入图片描述

在这里插入图片描述

假如在最后对标准输出进行刷新:
fflush(stdout) ;
则会将要打印的数据写入到 1 号文件描述符位置,但是当前 1 号文件描述符对应的是 tmp.txt 文件,即将要写入的数据写入 tmp.txt 文件当中。

在这里插入图片描述

重定向本质原理:

将一个描述符所对应位置的文件描述信息地址,给替换成另一个文件的描述--------实现了在不改变其他逻辑的情况下,改变了所操纵的文件。

int dup2(int oldfd, int newfd);
让 newfd 对应位置,保存 oldfd 对应位置的信息
将 newfd 重定向到 oldfd
若 newfd 本身代表了一个已经打开的文件,则重定向前会把文件关闭释放

dup2(fd,1); 将标准输出的内容输入到 fd 文件中

在 minishell 中实现重定向 > 与 >>

(1)捕捉用户输入,并进行分割;
(2)判断输入的命令是否存在重定向符号,一般重定向符号之后内容不是指令内容,而是要重定向到的文件信息;

   1  #include<stdio.h>                                                                    
    2 #include<stdlib.h>
    3 #include<string.h>
    4 #include<unistd.h>
    5 #include<sys/wait.h>
    6 #include<fcntl.h>        //open
    7 #include<sys/stat.h>   //open
    8 
    9 
   10 int main()
   11 {
   12   while(1){
   13     printf("【user@host~ 】$ ");
   14     fflush(stdout);    //刷新缓冲区
   15 
   16     char cmd[32]={0};            //用于存储键盘输入的字符串
   17     fgets(cmd,1023,stdin) ;      //获取键盘输入的字符
   18 
   19     cmd[strlen(cmd)-1]='\0';   //将键盘输入的字符后加上 \0 表示结束
   20 
   21     printf("[%s]\n",cmd);   //打印输入的字符内容
   22 
   23     int argc=0;
   24     char* argv[32]={NULL};    //用于存储分割后的字符内容
W> 25     char* ptr=cmd;   //保存获取到的字符信息
   26     argv[argc++]=strtok(cmd," ");        //以空格对字符串进行分割处理
   27 
28 
   29     while((argv[argc]=strtok(NULL," "))!=NULL){  //strtok 第一个参数 NULL ,表示默认>      以上一次终止位置作为新分割的起始位置进行分割
   30       printf("[%s]\n",argv[argc]);
   31       argc++;
   32     }
   33 
   34 //打印分割后的字符内容
   35     int i=0;
   36     for(;argv[i]!=NULL;++i){
   37       printf("[%s]\n",argv[i]);
   38     }
   39 //如果当前输入的第一个字符为 cd,则更改工作路径
   40     if(strcmp(argv[0],"cd")==0){
   41       chdir(argv[1]);      //更改当前工作路径
   42       continue;
   43     }
   44 
   45 
   46       
   47     //判断是否存在重定向符号                                                         
   48     // ls -l -a > a.txt              //默认重定向符号之后有个空格
   49     //
   50     int redir_flag=0;            //0-没有重定向,1-清空重定向,2-追加重定向
   51     char *redir_file=NULL;   //标记文件
   52     for(i=0;i<argc;++i){
53       if(strcmp(argv[i],">")==0){
   54         //清空重定向
   55         redir_flag=1;
   56         argv[i]=NULL; 
   57         break;          //重定向之后的内容为文件名,而不是指令内容
   58         redir_file=argv[i+1];
   59       }
   60       else if(strcmp(argv[i],">>")==0){
   61         //追加重定向
   62         redir_flag=2;
   63         argv[i]=NULL;
   64         break;
   65         
   66         redir_file=argv[i+1];
   67       }
   68     }
   69                                                                                      
   70 
   71 
   72     //创建子进程
   73     pid_t cpid=fork();
   74     if(cpid<0){
 75       perror("fork error!");
   76       continue;                                                                      
   77     }
   78     else if(cpid==0){
   79       //子进程实现程序替换
   80       //
   81       if(redir_flag==1){
   82         //清空重定向
   83         int fp = open("tmp.txt",O_RDWR|O_CREAT|O_TRUNC,0664);    //清空打开文件
   84         dup2(fp,1);
   85       }
   86       else if(redir_flag==2){
   87         //追加重定向
   88         int fp=open("tmp.txt",O_RDWR|O_CREAT|O_APPEND,0664);
   89         dup2(fp,1);    //将标准输出重定向到 fp 文件
   90 
   91       }
   92 
   93       execvp(argv[0],argv);
   94       perror("execvp perror");
   95       exit(-1);  //只有程序替换失败才会执行这行代码
   96     }
   97     wait(NULL); //每一个子进程运行完毕后,才能开始捕捉下一个输入进行操作
  98   }
   99 
  100   return 0;
  101 }


在这里插入图片描述
在这里插入图片描述

静态库 & 动态库

静态库

库文件
将已经实现的代码进行打包,并不是为了生成可执行程序,而是为了给其他人提供接口使用。

静态链接

生成可执行程序时,链接静态库,直接将库中所用到的函数实现拿到可执行程序当中,不存在依赖,运行效率高,但是生成的可执行程序很大----------------可能库中代码在内存中会存在冗余

动态库

以位置无关代码打包

动态链接

生成可执行程序时,链接动态库,记录库中符号表,生成程序小,并且多个程序运行时在内存中可以共享动态库内容,运行时依赖动态库的存在

生成库

生成库:把大量代码打包起来

(1)先把所以源码进行编译汇编生成各自的二进制指令 gcc *.c -o *.o
(2)把所有生成的二进制文件打包在一起

:vnew 文件名

新开一个 vim 窗口 ,命名为 当前给定的文件名

在这里插入图片描述
在这里插入图片描述

ctrl + ww :进行两个 vim 文件的跳转

静态库: ar -cr

Linux 下静态库命名:以 lib 为前缀,以 .a 为后缀

gcc -c child.c -o child.o
ar -cr libmychild.a child.o
生成一个名为 mychild 的静态库

在这里插入图片描述

动态库

linux 下动态库命名:以 lib 作为前缀,以 .so 作为后缀,中间是库名。

gcc -c child.c -o child.o

gcc -fPIC -c child.c -o child.o
gcc --shared child.o -o libmychild.so
生成一个名为 mychild 的动态库

在这里插入图片描述

-fPIC:告诉编译器,在编译生成指令的时候产生与位置无关代码(变量指令的地址都是相对偏移量)
一个动态库被映射到不同进程的虚拟地址空间时,映射的位置也不一定相同,所以在 main 中调用的库函数都只是记录一个相对偏移量,最后根据实际映射位置的起始地址进行计算。--------更加灵活

-c:只进行预处理,编译,汇编完毕后退出,不进行链接

–shared:告诉编译器要生成的是一个库文件,而不是可执行程序

使用库

直接运行 main 程序时会报错,因为未找到 printf_child 的定义:

在这里插入图片描述

使用 -l 来告诉编译器要使用哪个库

gcc main.c -o main -lmychid
mychild 是要链接的库文件

但是会报错,因为编译器会到系统指定目录下(/usr/lib64)来寻找库文件

在这里插入图片描述

因此我们可以

(1)将库放到指定位置下 :

x64------/usr/lib64 ; x32-----------/usr/lib

sudo cp libmychild.so /usr/lib64
//但是一般不推荐,因为这样会污染 /usr/lib64 库文件 

(2)设置环境变量:

export LIBRARY_PATH=${LIBRARY_PATH}:./
//将库文件所在目录添加到环境变量
//当前要使用的库文件位于 ./  当前目录下 
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:./

//添加运行加载库文件的路径

在这里插入图片描述

(3)使用 gcc -L 选项,指定库文件所在路径

gcc main.c -o main -L./  -lmychild

在这里插入图片描述

这种方式只适合在指定路径下,链接静态库,因为无法设置运行程序时动态库的加载路径

ps:
有任何疑问欢迎评论
留言~
~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值