Linux的文件操作

简介: Linux的文件操作

文件及其操作概念

文件 = 内容 + 属性。空文件也要在磁盘占据空间,因为空文件也有属性

文件操作 = 对内容 + 对属性或者对属性和内容

标定一个文件,必须使用:文件路径+文件名,如果没有加路径则默认在当前路径找文件

对文件接口写好但是程序没有运行前文件没有被操作,对文件的操作本质是进程和被打开文件的关系

一个文件要被访问前提必须要被打开,打开文件是用户去调用接口操作系统去打开。

进程可以打开多个文件,系统中会存在大量被打开的文件,操作系统会以链式结构管理起来会为文件创建对应的内核数据结构标识文件(struct file{})

**任何一个被打开的文件结构体对象struct file对象,不同的文件对应的读写方法不一样,struct file对象里面可以有很多的(readp)()、(writep)()函数指针,通过函数指针指向具体的读写方法

文件系统调用接口

首先要有个共识,所有高级语言的文件调用接口都是对系统的文件调用接口进行封装出来的

open:

打开文件,文件不存在就创建文件

6111d4daa54339c52010a17cf57fdd74.png

参数一为打开的文件名,参数二为打开方式(只读:O_RDONLY,只写:O_WRONLY,只读:O_RDWR,O_CREAT:文件不存在时创建文件,O_TRUNC:清空文件内容)参数三为新建时默认权限(文件不存在时创建文件必须加上参数三)

open成功时会返回文件描述符,失败返回-1close:关闭文件

99679f3b27353cce9391b156a8dc86b0.png

参数为文件的描述符

write:

向文件中写入

0d0a0bfbfc70384a426016019127afc5.png

参数一为文件描述符,参数二为输入的数据,参数三为输入数据的字节数

返回值为写入的字节数

使用这个接口可以用 sprintf(将特定的内容格式化到字符串)接口配合使用

62c41df9ef8b285fb3eaf4e0def565b8.png


19510529c43de8fa3cfac9c640e7efb1.png

read:

读取文件的数据


d4d7860384d2d743f1a1fcf353efea9a.png

参数一为文件描述符,参数二为存储数据的空间,参数三为空间的大小

调用成功则返回读取的字节数,0代表读到文件结尾


83ffb4b3e6856b7c08322a78854076eb.png

文件描述符

C语言中操作文件中的FILE是一个结构体,这里有个字段代表文件描述符,操作系统通过文件描述符标识文件

平常对文件操作时默认是从3依次对文件表示

0 -> stdin

1 -> stdout

2 -> stderr

每个进程都会有一个 file指针去指向一个指针数组,该数组存放指向文件的指针,所以文件描述符的本质就是数组的下标,进程可以直接通过下标找到文件。Linux下stdin、stdout、stderr这三个文件时默认打开的,所以当打开文件时就会从3开始,相对应的如果将0、1、2关闭后再打开文件那么该文件的文件描述符就会从最小没有被占用的下标开始

重定向

重定向的本质就是上层的fd不变,在内核中更改fd对应的struct_file*的地址,也就是改变文件描述符对应的文件

dup2:

将新打开的文件重定向到指定文件,将调用接口的进程所打开的文件的文件描述符的内容拷贝到指定的文件描述符,1=stdout, 3=myfile --> dup2(3, 1) --> 1=myfile, 3=myfile


df078e1ea684c3d37ac0d69914a78f7e.png

参数一为需要重定向文件的文件描述符,参数二为被重定向文件的文件描述符

调用失败返回-1,成功返回调用的文件描述符


db6dc35554d283a284aaddaa0bcf764b.png

追加重定向:

只要将打开文件的方式加上追加的选项就可以了

输入重定向:

可以指定从某个文件中读取,重定向0号文件描述符后就不会再在键盘中读取

实现简单的shell

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<assert.h>
#include<fcntl.h>
#include<string.h>
#define NUM 1024
#define OPT_NUM 64
//默认没有重定向
#define NOT_ 0
//输入重定向
#define INPUT_ 1
//追加重定向
#define ADDOUT_ 2
//输出重定向
#define OUTPUT_ 3
//标准错误重定向
#define ERROR_ 4
char Line[NUM];
char* myargv[OPT_NUM];
//记录退出状态
int lastCode = 0;
int lastSig = 0;
//利用数据表示是哪种重定向
int redirtype = NOT_;
//重定向文件的文件名
char* redirFileName = NULL;
//判断是否有重定向
void command(char* commands){
    assert(commands);
    //定义字符串的头尾指针
    char* start = commands;
    char* end = commands + strlen(commands);
    //循环遍历是否有重定向的符号
    while(start != end){
        if(*start == '>'){
            *start = '\0';
            ++start;
            //因为重定向有> 和 >>,所以还要再判断一次
            if(*start == '>'){
                *start = '\0';
                ++start;
                while(*start == ' ')
                    ++start;
                redirtype = ADDOUT_;
                redirFileName = start;
            }else{
                while(*start == ' ')
                    ++start;
                redirtype = OUTPUT_;
                redirFileName = start;
            }
            break;
        }else if(*start == '<'){
            *start = '\0';
            ++start;
            while(*start == ' ')
                ++start;
            //分隔开后就可以得到重定向的信息
            redirtype = INPUT_;
            redirFileName = start;
            break;
        }else{
            ++start;
        }
    }
}   
//通过进程替换编写shell
void Shell(){
    while(1){
        //初始化全局数据
        redirFileName = NULL;
        redirtype = NOT_;
        printf("用户名@主机名 当前路径#");
        //刷新缓冲区
        fflush(stdout);
        //获取输入指令,输入时会带着回车
        //需要去掉换行
        char* s = fgets(Line, sizeof(Line) - 1, stdin);
        assert(s != NULL);
        //清除最后的元素(换行)
        Line[strlen(Line) - 1] = 0;
        //考虑到重定向的情况所以需要先对读到的字符串分析一遍
        //判断是否会有 > < >> 符号
        command(Line);
        //要把指令和选项分开,以空格为分隔符
        //strtok
        myargv[0] = strtok(Line, " ");
        int i = 1;
        //给ls指令加上颜色选项
        if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
            myargv[i++] = "--color=auto";
        while(myargv[i++] = strtok(NULL, " "));
        //需要注意cd指令执行并不需要子进程去执行
        //因为子进程去更改当前工作目录对父进程没有影响
        //子进程执行完退出后还是没有更改父进程的当前工作路径
        //因此遇到cd指令时,使用内置接口chdir去执行
        //不需要子进程去执行而是让shell自己执行的命令就叫内建/内置命令
        if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0){
            if(myargv[1] != NULL)
                chdir(myargv[1]);
            continue;
        }
        //如果遇到echo指令也不需要子进程
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0){
            if(strcmp(myargv[1], "$?") == 0)
                printf("Code:%d, Sig:%d\n", lastCode, lastSig);
            //如果不是$? 则打印echo后面的所有字符串
            else{
                for(i = 1; myargv[i]; ++i)
                    printf("%s ", myargv[i]);
                printf("\n");
            }
            continue;
        }
        //创建子进程执行指令
        pid_t id = fork();
        assert(id != -1);
        if(id == 0){
            //因为命令是子进程执行的,所以重定向也是
            switch (redirtype)
            {
            case NOT_:
                //不执行操作
                break;
            case INPUT_:{
                //输入重定向
                int fd = open(redirFileName, O_RDONLY);
                if(fd < 0){
                    perror("open");
                    exit(2);
                }
                dup2(fd, 0);
                break;
            }
            case OUTPUT_:{
                //输出重定向
                int flags = O_WRONLY | O_CREAT;
                int fd = open(redirFileName, flags, 0666);
                if(fd < 0){
                    perror("open");
                    exit(2);
                }
                dup2(fd, 1);
                break;
            }
            case ADDOUT_:{
                //追加重定向
                int flags = O_WRONLY | O_CREAT | O_APPEND;
                int fd = open(redirFileName, flags, 0666);
                if(fd < 0){
                    perror("open");
                    exit(2);
                }
                dup2(fd, 1);
                break;
            }
            default:
                break;
            }
            execvp(myargv[0], myargv);
            exit(1);
        }
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        //取到退出状态
        assert(ret > 0);
        lastCode = (status >> 8) & 0xff;
        lastSig = status & 0x7f;
    }
}


目录
相关文章
|
2月前
|
Linux Windows
Linux系统中的文件操作
Linux系统中的文件操作
|
10月前
|
存储 Linux
Linux文件操作基础:快速入门指南和实用技巧
Linux文件操作基础:快速入门指南和实用技巧
59 0
|
8月前
|
存储 Linux 调度
【看表情包学Linux】系统下的文件操作 | 文件系统接口 | 系统调用与封装 | open,write,close 接口 | 系统传递标记位 O_RDWR,O_RDONLY,O_WRONLY...
【看表情包学Linux】系统下的文件操作 | 文件系统接口 | 系统调用与封装 | open,write,close 接口 | 系统传递标记位 O_RDWR,O_RDONLY,O_WRONLY...
62 1
|
2月前
|
Java Linux Shell
【Linux】13. 文件操作
【Linux】13. 文件操作
49 1
|
8月前
|
存储 Linux C语言
|
2月前
|
Linux
基于 Linux 的文件操作 网络编程的最后一环
Linux下万物皆文件 在了解了客户端和服务器的函数调用之后,我们只需要了解下文件操作就能编写出属于自己的客户端和服务器了,还能让他们进行通信。
30 0
|
2月前
|
存储 Linux C语言
Linux系统下C语言的文件操作
Linux系统下C语言的文件操作
34 0
|
12天前
|
Linux Shell Go
Linux中文件操作基本指令大全
Linux中文件操作基本指令大全
|
19天前
|
Linux 网络安全 开发工具
【linux】基础IO |文件操作符
【linux】基础IO |文件操作符
17 0
|
2月前
|
Linux C语言
【Linux】 拿下 系统 基础文件操作!!!
怎么样,我们的猜测没有问题!!!所以语言层的文件操作函数,本质底层是对系统调用的封装!通过不同标志位的封装来体现w r a+等不同打开类型! 我们在使用文件操作时,一般都要使用语言层的系统调用,来保证代码的可移植性。因为不同系统的系统调用可以会不一样!
27 2