基础IO(文件输入输出、标准IO接口、文件描述符和文件流指针)

 

目录

基础IO(文件的输入输出操作)

 FILE *fopen(char* filename,char* mode);(文件名称,打开方式)

size_t fread(char* buf,size_t block_size,size_t block_count,FILE* fp);(缓冲区,块大小,块个数,文件流指针);

size_t fwrite(char*data,size_t block_size,size_t block_count,FILE* fp);(数据首地址,块大小,块个数,文件流指针);

int fseek(FILE* fp,long offset,int whence);(将文件的读写指针从whence位置偏移offset个字节)- - - - -跳转文件读写位置

int fclose(FILE* fp);- - - - - 关闭文件流指针,释放资源

标准的IO接口(都是库函数,而库函数就是对系统调用接口的一层封装)

 int open(char* filename,int flag,mode_t mode);

ssize_t write(int fd,char* buf,size_t count);

ssize_t read(int fd,char* buf,size_t len);

off_t lseek(int fd,off_t offset,int whence);

int close(int fd);通过文件描述符关闭文件,释放资源;

int dup2(int oldfd,int newfd);- - - - -描述重定向函数

在minishell中实现>/>>标准输出重定向 

文件描述符与文件流指针的关系:



基础IO(文件的输入输出操作)

例:fopen/fwrite/fread/fseek/fclose        stdin/stdout/stderr

 FILE *fopen(char* filename,char* mode);(文件名称,打开方式)

打开方式:*r(只读); r+(读写);w(只写);w+(读写);a(追加写);a+(追加读写);b(二进制操作)。

追加写:每次写入数据总是写入到文件末尾

r+的读写和w+的读写的区别:r+读写打开文件,若文件不存在则报错;w+读写打开文件,若不存在则创建,若存在则清空原有内容。

a:不仅是追加写,并且文件不存在会创建新文件

b:默认清空如果不指定b,则认为文件是文本操作,加上b则认为是二进制操作;区别在于:有时候一个特殊字符,只是一个字符但是占据两个字节的内存(例:读取一个100字节大小的文件,文本操作最终读取出来的数据,不一定是100个字节)

返回值:返回实际一个FILE*的文件流指针作为文件的操作句柄;失败则返回NULL;

size_t fread(char* buf,size_t block_size,size_t block_count,FILE* fp);(缓冲区,块大小,块个数,文件流指针);

size_t fwrite(char*data,size_t block_size,size_t block_count,FILE* fp);(数据首地址,块大小,块个数,文件流指针);

  • 注意:fread/fwrite操作的数据实际大小=块大小*块个数;(例:块大小为10,块个数为2,则需要写入/读取20个字节的数据);
  • 返回值:返回实际操作的块个数;(例:读取一个文件size为10,count为2,如果文件大小足够则返回2,但是若文件大小只有16个字节,则返回1,因为第二块没有读满;fread如果读到了文件末尾则会返回0)
  • 如果读取1000个字节,块个数为1,文件大小只有512字节,虽然读取了512个数据但是也会返回0

int fseek(FILE* fp,long offset,int whence);(将文件的读写指针从whence位置偏移offset个字节)- - - - -跳转文件读写位置

int fclose(FILE* fp);- - - - - 关闭文件流指针,释放资源

fread/fwrite的块大小一般设定为1,块个数设为想要操作的数据长度

fseek:文件没有数据也可以跳转到读写位置;

对文件数据进行字符串操作是时候要注意文件数据中\0这种数据

#include <stdio.h>
#include <string.h>

int main()
{
    FILE *fp = NULL;

    fp = fopen("./test.txt", "r+");
    if (fp == NULL) {
        perror("fopen error");//打印上一个系统调用接口的使用错误原因
        return -1;
    }

    //fseek跳转读写位置 SEEK_SET-从文件起始偏移 SEEK_CUR-从当前读写位置开偏移
    //SEEK_END-从文件末尾偏移
    fseek(fp, 10, SEEK_END); //将文件的读写位置偏移到末尾

    //sizeof获取的是一块空间的大小 / strlen获取的是字符串的长度遇到\0截止
    char buf[] = "It's a fine day\0 today~~\n";
    int ret = fwrite(buf, strlen(buf), 1, fp);//(数据,块大小,块个数,流指针);
    if (ret == 0) {
        perror("fwrite error");
        return -1;
    }
    printf("write ret:%d\n", ret);

    fseek(fp, 0, SEEK_SET);

    char tmp[1024] = {0};
    ret = fread(tmp, 1, 1023, fp);//在块大小为1情况下不会出现读取到数据依然返回0的情况
    if (ret == 0) {
        printf("have no data or error\n");
    }
    printf("ret:%d-[%s]\n", ret, tmp);

    fclose(fp);
    return 0;
}

标准的IO接口(都是库函数,而库函数就是对系统调用接口的一层封装)

系统调用IO接口的学习:open\read\write\seek\close

 int open(char* filename,int flag,mode_t mode);

filename:要打开的文件名称;

flag:选项参数,文件的打开方式,必选项/可选项

  • 必选项(只能选择其一):O_WRONLY(只写);O_RDWR(读写);O_TRUNC(打开文件的同时清空原有内容);O_APPEND(追加写,总是将数据写入到文件末尾)。
  • mode:权限,如果使用了O_CREAT有可能创建新文件,就一定要指定文件权限,八进制数字形式。
  • 返回值:一个非负数,文件描述符,文件的操作句柄;失败返回-1;

ssize_t write(int fd,char* buf,size_t count);

fd:open返回的文件描述符,文件操作句柄,通过这个fd指定要往那个文件写入数据

buf:要写入文件的数据的空间首地址

count:要写入的数据大小

返回值:返回实际写入文件的数据字节长度;失败则返回-1

ssize_t read(int fd,char* buf,size_t len);

fd:open返回的文件描述符,文件的操作句柄;

buf:从文件中读取数据放到哪个缓冲区中的首地址;

len:想要读取的数据长度,注意这个len不能大于缓冲区的大小;

返回值:返回的是实际读取到的数据字节长度,错误返回-1;

off_t lseek(int fd,off_t offset,int whence);

fd:open返回的文件描述符;

offset:偏移量;

whence:从哪开始偏移;SEEK_SET(文件起始位置),SEEK_CUR(文件当前读写位置),SEEK_END(文件末尾);

返回值:成功返回当前位置相对于起始位置的偏移量;失败返回-1;

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main()
{
    int fd = -1;
    umask(0);//将当前进程的文件创建权限掩码设置位0-仅当前进程有效
    fd = open("./test.txt", O_RDWR|O_CREAT, 0777);
    if (fd < 0) {
        perror("open error");
        return -1;
    }
    lseek(fd, 10, SEEK_END);// 目的读写位置跳转到文件末尾
    char ptr[1024] = "hello world~\n";
    int ret = write(fd, ptr, strlen(ptr));
    if (ret < 0) {
        perror("write error");
        return -1;
    }
    printf("ret:%d\n", ret);

    lseek(fd, 0, SEEK_SET);// 跳转到文件的起始位置
    char buf[1024] = {0};
    ret = read(fd, buf, 1024);
    if (ret < 0) {
        perror("read error");
        return -1;
    }
    printf("ret:%d-[%s]\n", ret, buf);

    close(fd);
    return 0;
}

int close(int fd);通过文件描述符关闭文件,释放资源;

为什么打开一个文件,如果不操作一定要关闭,释放资源?

答:因为文件描述符实际上是有限的,若不关闭文件,文件描述符用光,则在进程中就打不开新文件了。

一个进程运行起来,进程会默认打开三个文件:标准输入 0-stdin/标准输出 1-stdout/标准错误 2-stderr;

文件描述符的分配原则:最小未使用;

print打印数据到标准输出,close(1)是把标准输出关闭了;打开新文件后,printf并没有把数据打印出来,而是在刷新缓冲区之后,两数据写入到了文件中。

printf并非帧的一定要把数据写入标准输出文件,而是因为printf函数中操作文件的时候操作的描述符是1。原本向1中写入数据就是向标准输出写入,然后当1指向了新的文件后,这个printf就会将数据写入到新的文件中重定向:将数据不再写入原本的文件,而是写入新的指定的文件中,实现方式就是替换这个描述符对应的文件描述信息。实际上是描述符的重定向,改变描述符所指向的文件,就是改变了数据的流向。

int dup2(int oldfd,int newfd);- - - - -描述重定向函数

功能:让newfd这个描述符也指向oldfd所指向的文件,这时候oldfd和newfd都能够操作oldfd所指向的文件。

**重定向实现原理**:每个文件描述符都是一个内核中文件描述信息数组的下标,对应有一个文件的描述信息用于操作文件,而重定向就是在不改变所操作的文件描述符的情况下,通过改变描述符对应的文件描述信息进而实现改变所操作的文件。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
    //close(1);//关闭0号描述符-就是关闭了标准输入
    /*
    umask(0);
    int fd = open("./test.txt", O_RDWR|O_CREAT, 0664);
    if (fd < 0) {
        perror("open error");
        return -1;
    }
    dup2(fd, 1);//将1重定向到test.txt这个文件
    printf("fd=%d\n", fd);
    fflush(stdout);//刷新标准输出缓冲区
    close(fd);
    */
    FILE *fp = fopen("./test.txt", "r+");
    fp->_fileno = 1;//将文件流指针中的文件描述符改成标准输出的描述符了
    fwrite("hello world\n", 1, 12, fp);
    fclose(fp);
}

在minishell中实现>/>>标准输出重定向

 

>清空重定向open(O_CREAT|O_TRUNC);

>>追加重定向opend(O_CREAT|O_APPEND);

a.txt  fd = open(a.txt);
dup2(fd,1);   //子进程在运行指令的时候,ls本身要将数据写入标准输出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    while(1) {
        //增加一个shell提示
        printf("[san@minishell]$ ");
        fflush(stdout);//刷新标准输出缓冲区
        //1. 等待标准输入
        char buf[1024] = {0};
        fgets(buf, 1023, stdin);
        buf[strlen(buf)-1] = '\0'; //buf[...]='\n'
        //1.5 解析重定向
        //ls -l  > a.txt
        char *ptr = buf;
        int redirect_flag = 0;
        char *redirect_file = NULL;
        while(*ptr != '\0') {
            if (*ptr == '>') {
                redirect_flag = 1;//这是清空重定向
                *ptr = '\0';//将>替换成结尾标志,则命令的解析到此位置就完毕了
                ptr++;
                if (*ptr == '>') {//有第二个>则是追加重定向
                    redirect_flag = 2;
                    *ptr = '\0';
                    ptr++;
                }
                while(*ptr == ' ' && *ptr != '\0') ptr++;//将a.txt之前的空格走完
                redirect_file = ptr;//redirect_file这个指针指向了a.txt中a的位置
                while(*ptr != ' ' && *ptr != '\0') ptr++; // 将a.txt字符走完
                *ptr = '\0';
            }
            ptr++;
        }
        //2. 对输入命令数据进行解析
        char *argv[32] = {NULL};
        int argc = 0;
        ptr = buf;
        // [    ls    -a    -l    ]
        while(*ptr != '\0') {
            if (*ptr != ' ') {
                argv[argc] = ptr;
                argc++;
                while(*ptr != ' ' && *ptr != '\0') {
                    ptr++;               
                }
                *ptr = '\0';
            }
            ptr++;
        }
        argv[argc] = NULL;//最后一个参数的下一个位置置NULL
        //3. 创建子进程 4. 在子进程中程序替换
        pid_t pid = fork();
        if (pid == 0) {
            if (redirect_flag == 1) {//清空重定向
                int fd = open(redirect_file, O_WRONLY|O_CREAT|O_TRUNC, 0664);
                dup2(fd, 1);//将标准输入重定向到redirect_file;原本要打印的数据就会被写入文件
            }else if (redirect_flag == 2){ // 追加重定向
                int fd = open(redirect_file, O_WRONLY|O_CREAT|O_APPEND, 0664);
                dup2(fd, 1);//将标准输入重定向到redirect_file;原本要打印的数据就会被写入文件
            }
            //execvp(char *file, char *argv[])  file--新程序名称 
            execvp(argv[0], (char**)argv);//程序替换成功就去运行新程序了,32行以后就不会运行了
            //能够走到第33行,那么肯定程序替换失败了
            perror("execvp error");//打印上一次系统调用接口使用的错误原因
            exit(0);
        }
        //5. 进程等待
        wait(NULL);
    }
    return 0;
}

文件描述符与文件流指针的关系:

文件描述符:是一个非负整数,系统调用的IO接口;在进程中每打开一个文件,都会创建有相应的文件描述信息struct file,这个描述信息被添加在pcb的struct files_struct中,以数组的形式进行管理,随即向用户返回数组的下标作为文件描述符,用于操作文件。

文件流指针:FILE结构体,typedef struct _IO_FILE FILE - - - - -库函数IO接口的操作句柄

通过文件流指针进行最终文件操作的时候,依然还要能够找到文件对应的文件描述符才可以,文件流指针是一个结构体,结构体中有很多的成员变量,其中有一个叫做_fileno- - -这就是文件描述符;

向文件写入数据,并不会直接写入文件,而是先写入缓冲区中,刷新缓冲区的时候才会写入文件;(只有库函数才存在这个缓冲区)

系统调用接口是直接将数据写入文件的,系统调用接口是没有这个缓冲区的。

exit( )退出会刷新缓冲区,而_exit( )退出时不会刷新缓冲区。

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值