【探索Linux】—— 强大的命令行工具 P.12(文件描述符 | 重定向 | 基础IO)

首页 linux 【探索Linux】—— 强大的命令行工具 P.12(文件描述符 | 重定向 | 基础IO)

一、open()函数返回值

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数。上篇文章中我们提到的 fopen、fclose、fread、fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。而, open、close、read、write、lseek 都属于系统提供的接口,称之为系统调用接口。

在Linux中,open()函数是用于打开文件的系统调用,它返回文件描述符。文件描述符是一个非负整数,用于唯一标识打开的文件。

open()函数的返回值有以下几种可能:

    如果成功打开文件,则返回一个新分配的文件描述符。
    如果发生错误,返回-1,并可以使用全局变量errno获取具体的错误代码。

常见的错误代码包括:

    EACCES:权限不足,无法访问指定文件。
    ENOENT:文件不存在。
    EISDIR:指定路径是一个目录。
    EMFILE:进程已经达到了最大允许的打开文件数。
    ENFILE:系统已经达到了最大允许的打开文件数。

使用时,我们一般需要检查open()函数的返回值是否为-1来判断是否出错,然后再根据具体的错误代码进行相应的处理。
二、文件描述符fd
1. 文件描述符的分配规则

文件描述符(File Descriptor)是一个整数,它用于标识一个进程打开的文件。在Unix/Linux系统中,每个进程都有一个文件描述符表(File Descriptor Table),用于管理打开的文件。文件描述符表是一个数组,每个元素表示一个打开的文件,而文件描述符就是数组的下标。

在Linux中,每个进程都有一个进程描述符(Process Descriptor)。进程描述符中包括一个文件描述符表,以及其他重要的信息,例如进程ID、进程状态等。当进程打开一个文件时,内核会从可用的文件描述符中选择一个最小的数字,并将其分配给该文件。这个数字就是文件描述符。

文件描述符一般从3开始分配,因为0、1、2已经被预留给标准输入、输出、错误了。当进程打开一个文件时,内核会从当前可用的文件描述符中选择一个最小的数字,并将其分配给该文件。如果所有的文件描述符都已经被占用,再次打开文件就会返回错误。

当进程关闭一个文件时,相应的文件描述符就会变为可用状态,可以重新分配给其他文件。文件描述符的数目是有限的,通常与操作系统的版本、硬件配置等有关。

🚨🚨注意:同一个进程中不同的文件描述符可以引用同一个文件,也就是说,打开同一个文件多次会得到不同的文件描述符。每个文件描述符都有自己的读写指针(File Offset),因此在不同的文件描述符上读写同一个文件,文件指针会互相独立,互不影响。

另外,在多进程编程中,父子进程共享文件描述符表。也就是说,如果一个进程打开了一个文件,并创建了一个子进程,那么子进程也会继承父进程的文件描述符表,包括已经打开的文件。这样,子进程就可以操作父进程已经打开的文件了。如果需要让子进程关闭父进程打开的文件,可以在fork()调用之后使用close()函数关闭不需要的文件描述符。
2. 文件描述符0、1、2

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2。这些文件描述符在进程启动时会自动打开,并与对应的设备文件关联起来。

具体来说:

    文件描述符0(stdin) 用于处理标准输入,也就是从键盘或管道中读取输入数据。
    文件描述符1(stdout) 用于处理标准输出,也就是将程序输出写入到屏幕上或者重定向到文件中。
    文件描述符2(stderr) **用于处理标准错误,也就是输出程序的错误信息到终端或者重定向到文件中。

这些文件描述符是预先打开的,因此无需使用open()函数来打开它们。使用时,可以直接使用文件描述符号码0、1、2来引用它们。
在这里插入图片描述
三、重定向

在Unix/Linux系统中,重定向是一种常用的操作,用于改变进程的标准输入、标准输出和标准错误输出的默认设备。通过重定向,可以将命令的输入来自文件,或者将输出和错误输出写入到文件中,而不是默认的终端设备。

重定向可以使用以下符号进行操作:

    标准输出重定向(>):使用大于号(>)将命令的标准输出写入到指定的文件中,并覆盖原有内容。如果文件不存在,则创建一个新文件。
    例如,将命令的输出写入到文件output.txt中:

    command > output.txt
        1

    标准输出追加重定向(>>):使用双大于号(>>)将命令的标准输出追加写入到指定的文件中,而不覆盖原有内容。如果文件不存在,则创建一个新文件。
    例如,将命令的输出追加写入到文件output.txt中:

    command >> output.txt
        1

    标准错误输出重定向(2>):使用大于号加数字2(2>)将命令的标准错误输出写入到指定的文件中,并覆盖原有内容。如果文件不存在,则创建一个新文件。
    例如,将命令的错误输出写入到文件error.txt中:

    command 2> error.txt
        1

    标准错误输出追加重定向(2>>):使用双大于号加数字2(2>>)将命令的标准错误输出追加写入到指定的文件中,而不覆盖原有内容。如果文件不存在,则创建一个新文件。
    例如,将命令的错误输出追加写入到文件error.txt中:

    command 2>> error.txt
        1

    标准输入重定向(<):使用小于号(<)将命令的标准输入从指定文件读取。
    例如,将命令的输入从文件input.txt中读取:

    command < input.txt
        1

除了文件外,重定向还可以使用特殊设备:

    /dev/null:空设备文件,将输出或错误输出重定向到/dev/null时,数据会被丢弃,类似于黑洞。例如:

    command > /dev/null   # 将命令的输出丢弃
    command 2> /dev/null  # 将命令的错误输出丢弃
        1
        2

重定向操作可以组合使用,例如:

command1 > output.txt 2>&1

    1

上述命令将命令command1的标准输出和标准错误输出都写入到文件output.txt中。

🚨🚨注意:重定向操作符(>、>>、2>、2>>、<)在使用时需要与命令之间有空格,否则会被当作命令的一部分处理。另外,重定向是由shell解释和处理的,不是由具体的命令实现的,因此在不同的shell中可能存在一些差异。
1. 重定向的本质

**重定向的本质是通过操作文件描述符来改变进程的输入输出。**当一个进程执行时,会从这三个文件描述符中读取输入或将输出写入其中。重定向就是改变这些文件描述符所对应的文件,从而改变进程的输入输出行为。

在重定向操作中,文件描述符0、1和2分别对应的是文件描述符表中的前三个元素,即0、1和2。通过操作文件描述符表,可以将文件描述符指向不同的文件或设备。

例如,当使用>符号重定向标准输出时,实际上是将文件描述符1所对应的文件更改为指定的文件。具体的操作步骤如下:

    打开指定文件,获取其对应的文件描述符x。
    将进程的文件描述符表中的1替换为x。

这样,当进程进行标准输出时,实际上是将数据写入到了指定的文件中。

类似地,其他重定向操作也是通过类似的方式改变文件描述符表中相应位置的文件描述符。

🚨🚨注意:重定向操作只影响当前进程以及其子进程,不会影响其他进程。每个进程都有自己独立的文件描述符表,互不干扰。
⭕图解

在这里插入图片描述
2. dup2 系统调用函数

dup2是Unix/Linux系统提供的系统调用函数之一,用于复制文件描述符并将其关联到指定的文件描述符。

⭕使用dup2函数可以方便地进行文件描述符的重定向,实现输入输出的灵活控制。

函数原型如下:

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

    1
    2

参数说明:

    oldfd:要被复制的文件描述符。
    newfd:新的文件描述符,将与oldfd关联。

函数功能:
dup2函数将文件描述符oldfd复制到newfd,并返回一个新的文件描述符。如果newfd已经打开,则将先关闭它。新的文件描述符将继承oldfd的文件状态标志(例如,文件偏移量、文件访问模式等)。

函数返回值:

    成功时,返回新的文件描述符;失败时,返回-1,并设置errno变量来指示错误类型。

使用示例(输入重定向):

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

int main() {
    int fd = open("file.txt", O_RDONLY);  // 打开文件,获取文件描述符
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    int newfd = dup2(fd, STDOUT_FILENO);  // 将文件描述符复制到标准输出
    if (newfd == -1) {
        perror("dup2");
        exit(EXIT_FAILURE);
    }

    printf("Hello, world!\n");  // 标准输出已被重定向到文件

    close(fd);  // 关闭文件描述符
    close(STDOUT_FILENO);
    return 0;
}
上述示例代码中,首先使用open函数打开一个文件,并获取文件描述符fd。然后,使用dup2函数将文件描述符fd复制到标准输出的文件描述符(即1),返回新的文件描述符newfd。接下来,通过printf函数向标准输出写入内容,实际上是将数据写入到文件中。最后,关闭文件描述符和标准输出的文件描述符。

149    2024-01-15 16:43:54