Linux输入输出重定向,我的代码实证和深度理解

事先申明,本文在理解Linux输入输出重定向方面,是有点干货的。有别于一般泛泛然的讲解,内容以源码为证,讲的比较实在
Linux的shell命令的高级操作,主要有:
- 输出重定向,操作符是:>、>>
- 输入重定向,操作符是:<
- 管道操作,操作符是(|)
为了从原理上说明管道操作,我们首先得了解文件描述符。关于文件描述符的背景知识,请参看我发过的笔记《我当年的Linux系统编程笔记-文件描述符》,这里不再复述。这里只对文件描述符、标准文件描述符的概念进行一个大致的讲解。
1 文件描述符
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。在windows中,内核记录应用打开的对象,就是handle句柄,也是一个整数值,两者有相似之处。

在Linux中,在编写脚本的时候会频繁使用标准输入( stdin)、标准输出( stdout)和标准错误( stderr)。通过内容过滤将输出重定向到文件是我们平日里的基本任务之一。文件描述符是与某个打开的文件或数据流相关联的整数。文件描述符0、 1以及2是系统预留的,相当于每个进程的全局变量,它们被称为标准文件描述符。

2 Shell 输入重定向"<"的基本概念

命令语法:command < file
对该命令的解释是:将输入重定向到 file。
进一步的解释是:本来需要从键盘stdin获取输入的命令,会转移到文件file读取内容。
3 Shell 输入重定向"<"命令的例子
$ cat input.txt
Line1:This is a sample
Line2:123456abc
Line3:Just file!
$ wc -l < input.txt
3 (wc命令统计的行数)
注意:如果命令不能从标准设备(stdin)读取数据,这个命令就不能做输入重定向。举例如下:
$ cat wantToEcho.txt
Line1:This is wantToEcho file
$ echo < wantToEcho.txt
命令“ echo < wantToEcho.txt”不会显示任何数据到命令行上。因为echo命令没有从标准设备(stdin)读取数据的能力,所以不会执行成功。
4 代码实证理解
从程序的观点出发,输入重定向命令“command < file”中,Linux管道机制将操作符“<”右边的file打开后,输出到stdin。如果command具有从stdin读取数据的动作,就正好接收处理。不符合这种情况的命令,不可以用到输入重定向中来。
怎么证实这个观点呢?还是下面的代码"in-redirect.c"验证最为直接。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define BUF_LEN 1024
int main(void)
{
char read_buf[BUF_LEN +1];
ssize_t len_read;
len_read = read(0,read_buf,BUF_LEN); //这个0,就是stdin
if ( len_read < 0 )
{
perror("perror:file read error ");
exit(1);
}
read_buf[len_read]=0x0;
printf("read stdin:%s\n",read_buf);
return 0;
}
编译:gcc in-redirect.c -o in-redirect.out
下面是验证过程:
$ ./in-redirect.out
123456abc789 (此处手工输入)
read stdin:123456abc789
$ cat input.txt 下面一行是input.txt文件中的内容
Line1:This is a sample
$ ./in-redirect.out < input.txt
read stdin:Line1:This is a sample
in-redirect.out这个程序,会从stdin中读取内容,通过“<”把它重定向成向input.txt中读数据了。因为有了源码示例,所以这个对“<”的理解比较通透。
为什么上面的代码“read(0,...);”之前,不用先open打开呢?
这是因为“标准文件描述符”有别于其它“文件描述符”,它们是进程创建就默认打开着的,不用open。你可以理解为0、1、2这三个文件描述符是进程的全局变量。既然是全局变量,拿来就用,可以read,也可以write,就不用open了,因为进程在创建之初,就相当于为你open过了。
5 输入重定向(<)和输出重定向(>、>>)的区别

从系统编程的角度来理解,输出重定向"command > file"就是:command命令输出数据,向stdout或stderr输出(write)数据,Linux Shell把这些数据重新定向(open)输出(write)到file文件中。也就是说:输出重定向就是对stdout或stderr进行重定向。
而输入重定向“command < file”,则是把Linux Shell把文件打开(open),write到stdio中去,然后command来读取(read)stdio的数据并进行处理。也就是说:输入重定向,就是对stdin进行重定向。
6 输入重定向和输出重定向混用
其实,只要符合输入输出重定向的语法,内部再符合标准输出输出的stdout、stdin、stderr的读read写write要求,都可以组合使用,不管命令有多么长。
比如上面的命令:wc -l < input.txt (统计input.txt的行数),如果我们想把它再输出到一个计数文件cout.txt中,可以这样做
$ cat input.txt
Line1:This is a sample
Line2:123456abc
Line3:Just file!
$ wc -l < input.txt > cout.txt
$ cat cout.txt
3
这两个命令和执行结果的说明:
wc -l < input.txt > cout.txt :wc命令本来要从stdin中读取数据,结果被输入重定向了(<),变成从文件input.txt中去读数据了。输出的结果本来要输出到stdout屏幕,结果被输出重定向了(>),变成了输出到文件cout.txt中了。