其他分享
首页 > 其他分享> > 7.文件重定向和管道

7.文件重定向和管道

作者:互联网

标准文件描述符

文件描述符(file descriptor)的概念:文件描述符是一个大于等于0的整数,它的含义是进程的文件打开表中项目的序号(从0开始)。对于一个进程而言,文件描述符是操作一个打开文件(或是一个设备文件,或是一个socket连接)的句柄。该条项目会存储指向系统级文件打开表相应条目的指针。系统级文件打开表会存储打开文件所对应的文件控制块(FCB)。

Unix(包括Linux)的特色之一是提供了大量的小的软件工具。这些软件工具都是基于标准I/O,从而可以使用重定向和管道更加灵活地使用这些工具。标准文件描述符包括标准输入(stdin)标准输出(stdout)标准错误输出(stderr),分别对应于文件描述符0、1、2。任何软件工具,都是从标准输入读取输入,把输出结果写入标准输出,将错误提示(如perror())写入标准错误输出。

很显然,我们编写程序时并没有建立0、1、2这三个文件描述符。这三个文件描述符是从父进程(shell)继承得来的,全部都连接于终端设备(即键盘和显示器)。

最低可用文件描述符(lowest-available file descriptor)原则

既然文件描述符本质上为进程文件打开表的条目序号,当我们关闭某个文件,也就释放了文件打开表中对应的条目,即该文件描述符可用。下次我们再打开一个文件时,就会使用最小数字的文件描述符,以确保文件打开表尽量的小。

我们通过以下程序可以看到进程始终使用最低可用的文件描述符。

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

int main() {
    int fd1,fd2;
    fd1 = open("/usr/include/stdio.h", O_RDONLY);
    printf("%d\n", fd1);

    fd2 = open("/usr/include/stdio.h", O_RDONLY);
    printf("%d\n", fd2);
    
    close(0);
    printf("close stdin\n");

    close(fd1);
    close(fd2);

    fd1 = open("/usr/include/stdio.h", O_RDONLY);
    printf("%d\n", fd1);

    fd2 = open("/usr/include/stdio.h", O_RDONLY);
    printf("%d\n", fd2);

    close(fd1);
    close(fd2);

    return 0;
}

运行结果:

image-20211207190121374

I/O重定向

例:在shell中使用I/O重定向

在本例中,who的输出本来是终端,使who输出重定向到文件后,文件是who输出的内容

image-20211207190338186

既然任何的软件工具都是从“标准输入”读取输入,“标准输出”输出结果。则如果我们能够提前将标准输入连接至某个文件,则可使进程以为自己从终端读用户的输入,而实则从文件中读取数据。对于标准输出和标准错误输出亦是同理。

I/O重定向方法1:close … open

close(0)关闭标准输入,文件描述符0可用。这时再打开文件,该文件连接到标准输入。此时再从标准输入读取,将读得文件内容。

close(1)关闭标准输出,文件描述符1可用。这时再打开文件,该文件连接到标准输出。此时再向标准输出,如printf,将不会在终端显示该内容,而是写入到文件。

例:printf的内容重定向到文件

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

int main() {
    int fd;
    close(1);
    fd = open("haha", O_WRONLY | O_CREAT, 0644);

    printf("%d\n", fd);

    return 0;
}

I/O重定向方法2:open… close… dup… close

当某些情况下,一个文件描述符已经打开,比如一个socket连接。这时已经来不及使用方法1。这种情况下,可以先关闭标准文件描述符,获得低可用文件描述符;再对希望重定向的文件描述符进行复制(dup)。此时标准文件描述符已连接至我们希望连接的文件上。将原有文件的文件描述符关闭。完成重定向。

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

main(){
    int fd;
    fd=open("haha",O_WRONLY|O_CREAT,0644);
    
    close(1);
    dup(fd);
    close(fd);
    
    execlp("who","who",NULL);
}

I/O重定向方法3:open… dup2… closed

方法3是方法2的简化。用dup2(fd, 0)替换close(0)dup(fd)

I/O重定向方法1和2的示意图如下:

image-20211229191741306

管道

我们平时所谓的管道(pipe)一般特指无名管道:直观上是将一个进程运行的结果作为另一个进程的输入。也可以将多于两个的进程用管道依次连接起来。

例如,在本例中,sort将ls的输出内容排序了

image-20211229192049169

管道将一个进程的标准输出与另外一个进程得标准输入相连。因为每一个软件工具都是从“标准输入”读,往“标准输出”写。这样就可以实现一个数据流的串联处理。

注意:

  1. 管道的下部是在内核里的,即管道要受内核的管理和控制;
  2. 管道是有方向的,如同家里的下水管——不是双向的。
管道的创建

通过pipe()系统调用可以在单个进程上创建一个管道。

查看man手册pipe(2)。pipe的调用格式为ini pipe(int pipfd[2]); 其中pipefd[0]是读数据端文件描述符,pipefd[1]是写数据端文件描述符。

image-20211229204222479

再通过fork()系统调用复制该进程。由于子进程继承父进程的一切,包括文件描述符,所以能获得如下图的状态。

image-20211229204243644

此时父子进程间就建立了管道。关闭不需要的文件描述符,再将父子进程分别运行我们希望运行的程序。无名管道就实现了。

例:该程序演示管道两端的方向性。

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

main(){
    int pipefd[2];
    char buf[1024];
    int n;
    pipe(pipefd);

    write(pipefd[1], "haha", 4);
    n=read(pipefd[0],buf,sizeof(buf));

    printf("%.*s\n", n, buf); 
}

例:实现 cat /usr/include/stdio.h|sort

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

main(){
    int pipefd[2];
    int rv;
    pipe(pipefd);

    rv=fork();
    if(0==rv){
        close(pipefd[1]);
        close(0);
        dup(pipefd[0]);
        close(pipefd[0]);
        execlp("sort", "sort", NULL);
    }else{
        close(pipefd[0]);
        close(1);
        dup(pipefd[1]);
        close(pipefd[1]);
        execlp("cat", "cat", "/usr/include/stdio.h", NULL);
    }
}

例:实现 cat /usr/include/stdio.h|sort

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

main(){
    int pipefd[2];
    int rv;
    pipe(pipefd);

    rv=fork();
    if(0==rv){
        close(pipefd[1]);
        close(0);
        dup(pipefd[0]);
        close(pipefd[0]);
        execlp("sort", "sort", NULL);
    }else{
        close(pipefd[0]);
        close(1);
        dup(pipefd[1]);
        close(pipefd[1]);
        execlp("cat", "cat", "/usr/include/stdio.h", NULL);
    }
}

标签:文件,重定向,int,pipefd,管道,描述符,close,include
来源: https://blog.csdn.net/AIbeichen/article/details/122749655