【Linux】文件操作、文件描述符和重定向(上)

简介: 【Linux】文件操作、文件描述符和重定向(上)

👉重新谈论文件👈


空文件也要占据磁盘空间。

文件等于文件内容加文件属性。

文件操作等于对文件内容的操作、对文件属性的操作或对文件内容和属性的操作。

标识一个文件,必须使用文件路径加文件名(具有唯一性)。

如果没有指明对应的文件路径,默认是在当前路径(进程的工作路径)进行文件访问。

当我们把 fopen、fclose、fread 和 fwrite 等接口写完之后,代码编译链接形成可执行程序之后,没运行可执行程序,文件对应的操作也没有被执行。故对文件的操作,本质是进程对文件的操作。

一个文件如果没有被打开,不可以进行文件访问。一个文件要被访问,就必须先被用户进程和操作系统打开。

并不是所有的磁盘文件都被打开了,磁盘文件可分为两种:被打开的文件和没有被打开的文件。所以,文件操作的本质是研究进程和被打开文件的关系。(注:文件系统里研究没有被打开的文件)



👉回顾 C 语言的文件操作👈


C 语言有文件操作,C++ 也有文件操作,任何一门语言都会有文件操作,而这些语言的操作接口都不一样!因为这些语言的文件操作接口都不一样,学习的成本是挺高的。那如何降低学习成本呢?我们知道:文件是在磁盘里的,磁盘是硬件。所有人想访问磁盘就不能绕过操作系统,那么开发者就必须使用操作系统提供的文件级别的系统调用接口。所以无论上层语言如何变化,库函数底层实现都必须调用系统调用接口。那么库函数可以千变万化,但是底层是不变的。那我们学习不变的东西,就可以降低学习成本了。


现在我们来回顾一下 C 语言的文件操作接口,再来学习文件操作的系统调用接口。


以 w 的方式打开文件


注:fprintf 函数可以将格式化的数据写到指定的流中。以 w 的方式打开文件,文件不存在会自动创建;如果文件存在,先清空文件的内容再进行写入。


#include <stdio.h>
#define FILE_NAME "log.txt"
int main()
{
    // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
    // a(append,追加), a+(追加式写入)
    FILE* fp = fopen(FILE_NAME, "w");   // 没有指明路径,默认在当前路径进行文件操作
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int cnt = 5;
    while(cnt)
    {
        fprintf(fp,"%s:%d\n", "hello world", cnt--);
    }
    return 0;
}


1fa04f5a0a3f474291c6dfef40c46812.png

322473910ecf41a19f75fd6a7bf0f4cd.png


以 r 的方式打开文件


e3b8d27c999e4b0bb69335ad6adcd723.png

#include <stdio.h>
#include <string.h>
#define FILE_NAME "log.txt"
int main()
{
    // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
    // a(append,追加), a+(追加式写入)
    FILE* fp = fopen(FILE_NAME, "r");   // 没有指明路径,默认在当前路径进行文件操作
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    char buffer[64];
    // 读取sizeof(buffer)-1个字符,为\0留一个位置
    while(fgets(buffer, sizeof(buffer) - 1, fp) != NULL)
    {
        buffer[strlen(buffer) - 1] = '\0';  // 清除\n
        puts(buffer);
    }
    return 0;
}

5a21749b3bc34e30b11e027958f0efcb.png

db62dfee73b2466fad415207c89ac1da.png

以 a 的方式打开文件


#include <stdio.h>
#define FILE_NAME "log.txt"
int main()
{
    // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
    // a(append,追加), a+(追加式写入)
    FILE* fp = fopen(FILE_NAME, "a");   // 没有指明路径,默认在当前路径进行文件操作
    if(fp == NULL)
    {
        perror("fopen");
        return 1;
    }
    int cnt = 5;
    while(cnt)
    {
        fprintf(fp,"%s:%d\n", "hello world", cnt--);
    }
    return 0;
}

1eee3ceff57b49198a402ce95af559bf.png


5a6ab188013648d3bee70c28a4c401fa.png


打开文件的方式


93e0f8f6cec64d9e82eede1277942d35.png


如上就是我们之前学的文件相关操作。还有 fseek、ftell 和 rewind 等函数,在 C 语言部分已经学习过,大家可以自行复习一下。


👉文件操作的系统调用👈


open


上面所使用的的 C 语言文件操作函数都是通过调用相应的系统调用接口的,fopen 函数对应的是系统调用 open,fwrite 等函数对应的是系统调用 write,fclose 函数对应的是系统调用 close。


系统调用 open 的参数和返回值

第一个参数:文件路径+文件名。只提供文件名,默认在当前路径进行文件操作。

第二个参数:打开文件的方式。该参数是通过宏来表示不同的打开方式,如:O_RDONLY 只读方式打开文件,O_WRONLY 只写方式打开文件等。通过按位或可以实现不同的文件打开方式,原因是这些宏都是通过比特位的不同来标记不同的选项,也就是说一个比特位就是一个选项。需要注意的是,比特位的位置不能重复。

第三个参数:创建文件的起始权限权限。为打开的文件设置不同的权限。使用 C 语言文件操作函数创建出来的文件默认权限是 664,文件的权限等于起始权限 & (~umask),普通文件的起始权限是 666,目录文件的起始权限是 777。

成功打开文件时返回一个大于 0 的文件描述符,打开失败则返回 -1 并且设置错误码 errno。


O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写

05a5eabc584b4c0d81a1ae4ae7146189.png


通过比特位来传递信息示例:


#include <stdio.h>
// 不同的标记位表示不同的选项
// 下面的每个宏对应的数值,只有一个比特位是1,彼此的位置不重叠
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
void show(int flags)
{
    if(flags & ONE) printf("ONE\n");
    if(flags & TWO) printf("TWO\n");
    if(flags & THREE) printf("THREE\n");
    if(flags & FOUR) printf("FOUR\n");
}
int main()
{
    show(ONE);
    printf("--------------------\n");
    show(TWO);
    printf("--------------------\n");
    show(ONE | TWO);
    printf("--------------------\n");
    show(ONE | TWO | THREE);
    printf("--------------------\n");
    show(ONE | TWO | THREE | FOUR);
    return 0;
}

8462b9168f194c89ada39ad37482d9de.png

5b2e34e7e0894e849e2f8f404b9796a2.png

那么,系统调用 open 中的各种宏也是通过这种方式实现的。


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main()
{
    int fd = open(FILE_NAME, O_WRONLY);
    // assert(fd != -1);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    close(fd);  // close关闭文件,参数是对应的文件描述符
    return 0;
}

4b15ae9d2e684985b830515e4e14dd61.png


12387ca75e384796997ad73dbba20647.png

宏 O_WRONLY 只是写,没有对应的文件就打开失败,并不是没有对应的文件就自动创建。如果想要没有对应文件就自动创建,想要按位或上 O_CREAT。


e847957f5baa4ee6b3441be23e130d2c.png


83fe0e4ea3e6469f8b2958233f5c5353.png

现在虽然没有出错,但是创建出来的文件的权限却是全乱的。如果想要创建出来的文件的权限是不乱的,就需要传入 open 的第三个参数。


84a91327aa5f4992b3f6cbad5a91393c.png


b8588f27ccab476598a32b2cbe655516.png

如果我们不想要系统默认的权限掩码,可以通过 umask 函数来设置。

9cc41d57524a48e48b2e9ede481f988d.png

fd0defbcd1fb4c9990ffec4961e8f853.png

99e7c54deba048cfbe05027092543d9d.png

write

f9261b567f5647539a6662260be32336.png


打印被打开文件的文件描述符


6ac8a75c7ead4c5ba58f30cefb757b07.png


811ccaa0c407477b9b49698cb41c4a11.png

关于为什么被打开文件的文件描述符是 3,会在后面的内容里讲解,这也是埋下的一个小小的伏笔。


提示内容:在语言层面上,文件是分文本类文件和二进制类文件的;但再操作系统层面上,文件都是二进制的。

aa5fc9ab32fa4f93af0614e83a18cddf.png

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main()
{
    umask(0);   // 将权限掩码设置为0,文件的最终权限等于起始权限&(~umask)
    int fd = open(FILE_NAME, O_WRONLY | O_CREAT, 0666);
    if(fd < 0)
    {
        perror("open");
        return 1;
    }
    int cnt = 5;
    char outBuffer[64];
    while(cnt)
    {
        sprintf(outBuffer, "%s:%d\n", "hello world", cnt--);
        // C语言规定以\0作为字符串的结尾, 但与文件没有任何的关系, 所以下面的strlen不需要+1
        write(fd, outBuffer, strlen(outBuffer));
    }
    //printf("fd:%d\n", fd);
    close(fd);  // close关闭文件,参数是对应的文件描述符
    return 0;
}

0631269e99af46329d7879a55cc612be.png


9c69ea54f621497bbfd28dca1e2e14cb.png


如果我们上面的代码改成下面的样子,再运行起来并查看文件会出现上面情况呢?

f82592d3aacb4ca690270632e2d86be4.png

622092081c474c0c80a0ce6bcb2a6b55.png


可以看到:write 是覆盖式写入的,并不是先清空文件里的内容再进行写入。但是 C 语言的写入不是这样子的呀,C 语言的文件操作函数是对系统调用的封装。如果我们也想要实现文件存在时,先清空文件的内容再进行写入的话,还需要给 open 多传入一个宏 O_TRUNC。

9a9feea71c42455c83624e18b58aa529.png

36194ea7309d40378db1f98d4cbc181b.png


所以,C语言的fopen(FILE_NAME, "w")对应的系统调用就是open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666)。如果想以追加的方式向文件写入的话,只需要再给 open 再传入一个宏 O_APPEND。注:O_APPEND 不要和 O_TRUNC 一起使用。

7af6f10b0d1d4c039355baf066161cac.png

5bfea8ee1e314f7e8693708b49f6bc9f.png




















相关文章
|
3月前
|
人工智能 监控 Shell
常用的 55 个 Linux Shell 脚本(包括基础案例、文件操作、实用工具、图形化、sed、gawk)
这篇文章提供了55个常用的Linux Shell脚本实例,涵盖基础案例、文件操作、实用工具、图形化界面及sed、gawk的使用。
605 2
|
4月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
164 3
|
4月前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第17天】重定向在Linux中改变命令I/O流向,默认有&quot;&gt;&quot;覆盖输出至文件及&quot;&gt;&gt;&quot;追加输出至文件末尾,便于保存结果;使用&quot;&lt;&quot;从文件读取输入而非键盘,高效处理数据。文件描述符如0(stdin)、1(stdout)、2(stderr)标识I/O资源,支持读写操作。管道以&quot;|&quot;连接命令,使前一命令输出成为后一命令输入,如排序用户或找出CPU占用最高的进程,构建复杂数据处理流程。
50 9
|
4月前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第14天】输出重定向可将命令结果存入文件,如`&gt;`覆盖写入或`&gt;&gt;`追加写入。输入重定向从文件读取数据,如`&lt;`代替键盘输入。这些操作利用文件描述符(如0:stdin, 1:stdout, 2:stderr)管理I/O。管道`|`连接命令,使前一命令输出作为后一命令输入,便于数据处理,如排序用户`sort -t: -k3 -n /etc/passwd | head -3`或查找CPU占用高的进程`ps aux --sort=-%cpu | head -6`。
42 4
|
4月前
|
Unix Linux Shell
Linux I/O 重定向简介
Linux I/O 重定向简介
40 2
|
3月前
|
Unix Linux
linux中在进程之间传递文件描述符的实现方式
linux中在进程之间传递文件描述符的实现方式
|
4月前
|
运维 Rust 监控
Linux高效运维必备:fd命令深度解析,文件描述符管理从此得心应手!
【8月更文挑战第23天】本文介绍了一款名为fd的命令行工具,该工具基于Rust语言开发,旨在以更直观的语法和更快的速度替代传统的`find`命令。通过本文,您可以了解到如何安装fd以及一些基本用法示例,比如使用正则表达式匹配文件名、排除特定目录等。此外,文章还展示了如何结合`ps`和`lsof`命令来查找特定文件并显示其文件描述符,从而帮助您更好地管理和监控Linux系统中的文件与进程。
142 0
|
4月前
|
存储 Linux 数据处理
在Linux中,管道(pipe)和重定向(redirection)的是什么?
在Linux中,管道(pipe)和重定向(redirection)的是什么?
|
15天前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
103 6