> 文章列表 > 【hello Linux】基础IO

【hello Linux】基础IO

【hello Linux】基础IO

目录

1.C语言文件操作

1. 打开文件:

2. 文件操作

3. 关闭文件

2. C语言中的流操作

3. 系统文件IO

1. 接口介绍

2. 写文件

3. 读文件

4. 文件描述符fd

0 & 1 & 2 文件描述符: 

文件描述符原理:

文件描述符的分配规则:

5. 重定向

6. 使用dup2系统调用

7. stdout和stderr的区别

stdout 重定向:

如何达到 stdout 和 stderr都重定向?


Linux🌷 

1.C语言文件操作

在进行文件操作前,我们都要先打开对应文件,文件操作后,我们要关闭对应文件。

C语言中进行文件操作要包含头文件:

#include <stdio.h>

1. 打开文件:

FILE *fopen( const char *filename, const char *mode );

 filename  :文件的路径 = 路径 + 文件名;

 mode  :打开方式:"r" 、"w"、"a" 等;

返回值:打开成功则返回一个指向打开文件的指针,打开失败则返回NULL;

打开方式详解: 

 "r"只读方式打开,若文件不存在则打开失败;

 "w"只写方式打开,若文件存在则清空文件,若文件不存在则创建新文件;

  "a"追加方式打开,若文件存在则在文件末尾追加要写入的内容,若文件不存在则创建新文件;

 2. 文件操作

写文件

int fputs( const char *string, FILE *stream );

将string的内容写到stream

示例: 

#include <stdio.h>    int main()    
{    FILE* fp = fopen("./log.txt","w");  //以写的方式打开文件    if(fp == NULL)        //打开文件失败    {    perror("fopen");    return 1;    }    int cnt=10;                         //写文件    const char* msg = "hello linux!\\n";    while(cnt--)    {    fputs(msg,fp);    }    fclose(fp);         //关闭文件    return 0;                                                                                                                                                        
}    

结果演示:

读文件

char *fgets( char *string, int n, FILE *stream );

将stream执行的内容读到string中,每次最多都=读n个字节;

示例:

  #include <stdio.h>    int main()    {    FILE* fp = fopen("./log.txt","r");    if(fp==NULL)    {    perror(fopen);    return 1;    }    char buffer[64];    while(fgets(buffer, sizeof(buffer),fp))    {    printf("%s",buffer);    }    if(!feof(fp))    {    printf("fgets quit not normal!\\n");    }    else    {    printf("fgets quit normal!\\n");    }    fclose(fp);    return 0;                                                                                                                                                                                                   }    

结果演示:

 追加文件

示例:

  #include <stdio.h>    int main()    {    FILE* fp = fopen("./data.txt","a");    if(fp==NULL)    {    perror(fopen);    return 1;    }    const char* msg = "hello linux!\\n";    int cnt = 3;    while(cnt--)    {    fputs(msg,fp);    }    fclose(fp);    return 0;                                                                                                                                                                                                   }    

结果演示:

 3. 关闭文件

int fclose( FILE *stream );

stream 为打开文件的文件标识符


在上述中,我们对文件的操作,最终都是访问硬件的:显示器、键盘、磁盘;

OS作为硬件的管理者,它不相信任何用户,提供了一些系统调用接口供用户使用;

C语言中的fgets、fputs便是在系统调用接口上进行了二次封装所得到的,这些函数调用比系统调用

要易于用户使用;


 2. C语言中的流操作

将内容输出到显示器上,我们能想到什么办法?

我们发现不光使用printf可以达到此功能,使用stdout也可以达到此功能;

在Linux学习中,我们知道一切皆文件,其实对于键盘、显示器等,我们都可以把他们当作文件来

对待,它们有读写函数,对应读写功能,经过驱动层的封装,我们可以像对平常文件文件一样,使

用读写函数对键盘、显示器进行读写操作;

在C语言程序中,会默认打开三个输入输出流:stdin、stdout、stderr

stdin:标准输入流,对应的设备为键盘:从键盘读入字符,将内容保存在键盘对应的文件中;

stdout:标准输出流,对应的设备为显示器:从显示器文件读出数据,自然我们便可以在显示器上

看到字符;

stderr:对应设备为显示器;

经过查看cpluplus我们也发现这三个流的类型都是文件类型:

 当我们的c语言程序运行起来的时候,该进程为bash的子进程,bash默认打开stdin、stdout、

stderr三个流,该进程继承父进程也打开这三个流,我们的printf、scanf等函数才可以向显示器打

印数据、从键盘读取数据。

3. 系统文件IO

上述C语言对文件操作的函数是在系统调用接口上的封装;

下来我们使用系统提供的接口完成上述对文件的读写操作:

1. 接口介绍

open
我们可以使用man手册进行查询:man 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: 要打开文件的:文件名及路径//flags: 打开方式
//打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。//参数:
//    O_RDONLY: 只读打开
//    O_WRONLY: 只写打开
//    O_RDWR : 读,写打开
//这三个常量,必须指定一个且只能指定一个
//    O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
//    O_APPEND: 追加写//mode:权限
//如果文件存在也可以不用此参数;
//若文件不存在便要使用此参数给创建的文件赋予权限,否则文件权限是乱的;//返回值:
//    成功:新打开的文件描述符
//    失败:-1

上述flags参数的选项是可以进行或运算的,那些选项都是只有一个比特位为1的数据,且不重复;

2. 写文件

写文件:写到哪、从哪写、一次写多少字节; 

#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    int main()    
{    int fd = open("myfile.txt", O_WRONLY|O_CREAT, 0644);    if(fd<0)      //打开失败    {    perror("open");    return 1;    }    int count = 5;    const char* msg = "hello linux!\\n";    int len = strlen(msg);    while(count--)    {    write(fd,msg,len);    }    close(fd);    return 0;                                                                                                                                                        
}    

 3. 读文件

读文件:从哪读、读到哪、一次最多读多少字节;

#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    int main()    
{    int fd = open("myfile.txt",O_RDONLY);    if(fd<0)    {    perror("open");    return 1;    }    const char* msg = "hello linux!\\n";    char buf[1024];    while(1)    {    ssize_t s = read(fd,buf,strlen(msg));    if(s>0)    printf("%s",buf);    else    break;    }    close(fd);    return 0;                                                                                                                                                        
}    

4. 文件描述符fd

 在我们使用系统调用接口open打开一个文件时,open系统调用接口的返回值便称为文件描述符;

文件描述符的取值:0、1、2、3....正整数;

0 & 1 & 2 文件描述符:

Linux 进程默认情况下会 打开三个文件描述符:分别是标准输入 0 , 标准输出 1 , 标准错误 2;
0,1,2 对应的物理设备一般是:键盘,显示器,显示器
所以输入输出还可以采用如下方式:
  #include <stdio.h>    #include <sys/types.h>    #include <sys/stat.h>    #include <fcntl.h>    #include <string.h>    int main()    {    char buf[1024];    ssize_t s = read(0,buf,1024);    if(s>0)    {    buf[s]='\\0';    write(1,buf,strlen(buf));    write(2,buf,strlen(buf));    }    return 0;                                                                                                                                                      }    

 文件描述符原理:

文件描述符就是从 0 开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构
来描述目标文件。于是就有了fifile 结构体。表示一个已经打开的文件对象。而进程执行 open 系统调
用,所以必须让进程和文件关联起来。每个进程都有一个指针*fifiles, 指向一张表 fifiles_struct, 该表
最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文
件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件

文件描述符的分配规则:

我们通常打开一个文件时,被分配的文件描述符为3,因为0、1、2被占用;

#include <stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
#include <unistd.h>    
#include <string.h>    int main()    
{    int fd1 = open("mm1",O_WRONLY|O_CREAT, 0644);    int fd2 = open("mm2",O_WRONLY|O_CREAT, 0644);    int fd3 = open("mm3",O_WRONLY|O_CREAT, 0644);    printf("fd1:%d\\n",fd1);    printf("fd2:%d\\n",fd2);    printf("fd3:%d\\n",fd3);    close(fd1);    close(fd2);    close(fd3);                                                                                                                                                      
}    

经过证实:确实是从3开始分配;

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
#include <sys/stat.h>    
#include <fcntl.h>    int main()    
{    close(0);    int fd = open("aa",O_WRONLY|O_CREAT,0664);    printf("fd:%d\\n",fd);    return 0;    
}  

 我们将0号标识符关闭,发现新创建的文件的文件标识符为0;

总结:
文件描述符的分配规则:在 fifiles_struct 数组当中,找到当前没有被使用的最小的一个下标,作为
新的文件描述符。

5. 重定向

  #include <stdio.h>    #include <sys/types.h>    #include <sys/stat.h>    #include <fcntl.h>    #include <stdlib.h>    int main()    {    close(1);    int fd = open("stdout",O_WRONLY|O_CREAT,0644);    if(fd<0)    {    perror("fopen");    return 1;    }    printf("fd:%d\\n",fd);    fflush(stdout);                                                                                                                                                close(fd);    return 0;    }    

在上述代码中,我们将1号端口关闭,新创建的文件的文件标识符被赋予1;

1号原本是标准输出的,对应着显示器,现在对应这新创建的文件;

因此原本应该输出到显示器的内容,写入到了文件中;

这便是输出重定向;

 图解表示:

 6. 使用dup2系统调用

使用方法:

#include <unistd.h>int dup2(int oldfd, int newfd);

将老的文件标识符所指的内容拷贝至新的文件标识符;

也就是说老的文件标识符和新的文件标识符所指的内容都是老的文件标识符所指的内容;

#include <stdio.h>    
#include <unistd.h>    
#include <fcntl.h>    int main()    
{    int fd = open("./log", O_CREAT|O_RDWR);    if(fd<0)    {    perror("open");    return 1;    }    close(1);    dup2(fd,1);    int i=0;    for(;i<2;i++)                                                                                                                                                                                                 {    char buf[1024] = {0};    ssize_t read_size = read(0, buf, sizeof(buf)-1);    if(read_size < 0)    {    perror("read");    break;    }    printf("%s",buf);    fflush(stdout);    }    return 0;    
}    

上述是一个从标准输入流中读取两行数据,原本输出到标准输出流中,但是进行了dup2系统调

用,使原本输出到标准输出流中的数据,输出到log文件中。

 可见:使用此函数可以完成重定向功能。

7. stdout和stderr的区别

stdout 重定向:

先看一个代码:

#include <stdio.h>    
#include <string.h>    
#include <unistd.h>    int main()    
{    const char* msg1 = "hello 标准输出\\n";    write(1,msg1,strlen(msg1));    const char* msg2 = "hello 标准错误\\n";    write(2,msg2,strlen(msg2));    return 0;                                                                                                                                                           
}

将msg1和msg2分别输出到标准输出流和标准错误流中;

经过查看确实如此;

但当我们将程序执行的结果重定向至log文件中,发现标准错误的还是显示到显示器上,标准输出的才是重定向至log文件中;

这是我们便发现其实 > 是输出重定向,只重定向 stdout 的内容;

 如何达到 stdout 和 stderr都重定向?

此语句的意思是将 1 指向的内容拷贝至 2 ;

具体点来说:

原本 1 指向 stdout ,2 指向 stderr,现在将 1 先指向 log 文件,最后将 1 所指的内容拷贝至 2 ,也就是说 1 和 2 最后都指向 log 文件;

坚持打卡!😃