IO

要弄清什么是标准输入输出。首先需要弄懂什么是IO。
IO的I是Input的意思,O是output的意思。
意味着输入和输出。
更确切的含义是
I:从外部设备输入到内存
O:从内存输出到外部设备

标准输入、输出

linux中一切设备皆是文件!
因此标准输入和输出更具体的含义是文件。

它们是/dev/stdin这个文件和/dev/stdout这个文件。
也就是说所谓的标准输入和标准输出其实就是两个linux下的文件。

linux的文件类型有:
1、普通文件2、字符设备文件3、块设备文4、目录文件
5、链接文件6、管道文件7、套接字文件

标准输入逻辑上来看:
就是打开/dev/stdin这个文件,然后把这个文件里的内容读进来。
输出到标准输出逻辑上来看:
就是打开/dev/stdout这个文件,然后把内容输出到这个文件里去。

为什么是从逻辑上来看?因为它们不是设备文件!!!
所以它们不代表一个设备。linux里一切皆是文件,设备是文件,但是文件不一定是设备!
那它们是什么文件?他们是链接文件。(可以用ls -l /dev来查看 l开头的就是链接文件。)
文件内容是另一个文件的地址的文件称为链接文件。
因此,打开、读或者写 /dev/stdin和/dev/stdout 实际上是打开、读或者写这两个文件存放的地址对应的设备文件。

文件描述符

linux进程每打开一个文件都会返回一个文件描述符(整数)。
这个描述符实际是打开的文件在该进程的描述符表上的偏移值。
比如说p是描述符表,1是描述符,那么p[1]就能够索引到1描述符对应的打开文件。
有了这个偏移值(文件描述符)就能够快速的找到并操作文件。

(当然实际的情况是这个文件描述符能够索引到打开文件表表项,然后再通过打开文件表表项索引到对应的V-node节点表表项,而这个v-node节点表表项才代表真正的文件。不过只从逻辑上来看不需要理解这个括号里的说明。)

文件 文件描述符 链接文件 文件
输入文件—标准输入 0(缺省是键盘,为0时是文件或者其他命令的输出) stdin /proc/self/fd/0
输出文件—标准输出 1(缺省是屏幕,为1时是文件) stdout /proc/self/fd/1
错误输出文件—标准错误 2(缺省是屏幕,为2时是文件) stderr /proc/self/fd/2

linux重定向标准输入说明

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd1,fd2;
    
    fd2 = open("p1.py",O_RDONLY,0);
    dup2(fd2,0);
    char c;
    while( read(0,&c,1) > 0)
        printf("%c",c);
    close(fd2);

    fd1 = open("/dev/stdin",O_RDONLY);

    printf("%d\n",fd1);
    while( read(fd1, &c, 1) > 0)
        printf("%c",c);

    return 0;
}

程序解释

1、首先打开了一个叫做p1.py的文件。
2、然后用dup2这个函数使得文件描述符0这个位置的指针指向文件描述符fd2这个位置的指针指向的文件。
也就是说本来是这样:p[0] = &fiel1,p[fd2] = &file2,现在p[0] = p[fd2] = &file2。
而我们都知道文件描述符0这个位置对应的文件file1是标准输入文件/dev/stdin。
那么这个函数的意义就是把标准输入重定向到了p1.py这个文件里了。

之后再用标准输入比如说scanf(),来读,那么都是从p1.py这个文件里读了。
3、然后把这个文件里的东西读出来并输出到屏幕上。
4、打开/dev/stdin这个文件,这个文件是标准输入。
5、把这个文件里的东西读出来并打印到屏幕上。

预期结果

执行到第5步应该停下来,等待键盘输入。
然后把输入的东西打印到屏幕。

实际结果

没有等待键盘输入,它直接把p1.py的文件内容输出到屏幕上,也就是说和上面的输出是一样的!!!

原因

这是因为,标准输入这个文件/dev/stdin是个链接文件!!!
它存放的是别的文件的地址!!!
如果文件描述符指向的文件是个普通文件,那么把这个文件描述符指向别的文件,就是真的指向了别的文件。
而这里的文件描述符指向的是个链接文件,那么把这个文件描述符指向别的文件,意味着什么???意味着它
把这个链接文件里的内容(地址)更改了,而它仍然指向这个文件。只不过它知道这是个链接文件,因此它会访问的是这个链接文件中的地址对应的文件。

理解了上面的操作就可以说得通了,dup2对于链接文件只是修改了文件中的地址,它并没有真正指向别的文件,这也导致了一个问题,那就是它把这个链接文件给修改了,如果进程再次打开这个链接文件,那么链接文件之前存的地址就没有了,因此执行

fd1 = open("/dev/stdin",O_RDONLY);
这个的时候,实际上是又一次打开了p1.py这个文件!!!因为/dev/stdin这个文件里存放的地址已经是p1.py这个文件的地址了。。。

标准输入输出重定向

绑定重定向

  • Command >&m
    把标准输出重定向到文件描述符m中
  • Command < &-
    关闭标准输入
  • Command 0>&-
    同上

输出重定向

  • Command > filename
    把标准输出重定向到一个新文件中
  • Command >> filename
    把标准输出重定向到一个文件中(追加)
  • Command > filename
    把标准输出重定向到一个文件中
  • Command > filename 2>&1
    把标准输出和错误一起重定向到一个文件中,2>&1表示将错误输出绑定到文件描述符1上,1即标准输出
  • Command 2 > filename
    把标准错误重定向到一个文件中
  • Command 2 >> filename
    把标准输出重定向到一个文件中(追加)
  • Command >> filename2>&1
    把标准输出和错误一起重定向到一个文件(追加)
  • command 2> /dev/null
    如果command执行出错,将错误的信息重定向到空设备

标准输入重定向

  • Command < filename > filename2
    Command命令以filename文件作为标准输入,以filename2文件作为标准输出
  • Command < filename
    Command命令以filename文件作为标准输入
  • Command << delimiter
    从标准输入中读入,知道遇到delimiter分界符