在个人学习《Linux Shell脚本攻略》第一章时,到第1.6节“玩转文件描述符及重定向”中,发现有一处言语不明(也或者是例子不当之处)。
1 文件描述符
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。在windows中,内核记录应用打开的对象,就是handle句柄,也是一个整数值,两者有相似之处。
在Linux中,在编写脚本的时候会频繁使用标准输入( stdin)、标准输出( stdout)和标准错误( stderr)。通过内容过滤将输出重定向到文件是我们平日里的基本任务之一。文件描述符是与某个打开的文件或数据流相关联的整数。文件描述符0、 1以及2是系统预留的,相当于每个进程的全局变量。
2 重定向试验与理解
于是开始按照示例来试。
~$ echo "A sample test" echo命令把这段文字输出到 stdout
A sample test
~$ echo "A sample test to out.txt" > out.txt
上面一行命令,把本来要输出到stdout文件(屏幕),重定向到磁盘文件out.txt中
~$ cat out.txt
A sample test to out.txt 显然是重定向成功了
~$ echo "A sample test append to out.txt" >> out.txt
上面一行命令,把本来要输出到stdout文件(屏幕),重定向到磁盘文件out.txt中,并且加到尾部
~$ cat out.txt
A sample test to out.txt
A sample test append to out.txt
显然是重定向成功了,内容也成功的加到了尾部
前面这一部分非常简单。
~$ ls +abcd 故意让这个命令出错,出错信息会输出到stderr
ls: 无法访问 '+abcd': 没有那个文件或目录 出错信息
$ ls +abcd >stderr.txt
ls: 无法访问 '+abcd': 没有那个文件或目录 出错信息
~$ cat stderr.txt
这一行命令,什么也不会输出,因为出错信息是输出到stderr中的
~$ ls +abcd 2>stderr.txt
~$ cat stderr.txt
ls: 无法访问 '+abcd': 没有那个文件或目录
出错信息重定向到了stderr.txt中
重定向的命令语法是:cmd [参数] 文件描述符>文件
如果“文件描述符”不输入的法,默认是1,也就是stdout。比如:echo "a test" > out.txt, 相当于:echo "a test" 1> out.txt。所以上面的“ls +abcd 2>stderr.txt”才能把出错信息重定向到stderr.txt文件中。红字“2”就是指stderr。
3 多重定向命令的理解
如上图,执行“ls + 2>stderr.txt 1>stdout.txt”后,这个命令是想达到:stderr单独重定向到磁盘文件stderr.txt,将stdout重定向到另一个磁盘文件stdout.txt。结果:可以cat到stderr.txt有内容,但stdout.txt没有任何内容。
没有得到合适的观察结果,让人怀疑是错了?
没办法,自制了一个命令,C代码文件:redirection_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
printf("This line out to stdout \n"); //输出到stdout
perror("This line out to stderr "); //输出到stderr
return 0;
}
使用“gcc redirection_test.c -o redirection_test.out”编译成可执行程序redirection_test.out。
$./redirection_test 2>stderr.txt 1>stdout.txt
这一行命令,故意让这个命令出错,出错信息会输出到stderr
$ cat stderr.txt
This line out to stderr : Success
$ cat stdout.txt
This line out to stdout
其实,脚本攻略上讲的没有错,只是我没用到合适的测试程序。“ls +”出错后,只向stderr输出信息,而没有向stdout输出信息,所以测不出来。
4 系统保留文件描述符
前面提到过:文件描述符0(stdin)、 1(stdout)以及2(stderr)是系统预留的,相当于每个进程的全局变量。
每个进程创建时,内核为进程打开的文件创建了一个文件描述符表,该表的每一条都记录了进程打开的文件,这个表格的索引序号就是文件描述。
那么,stdin、 stdout以及stderr,这三个妖怪在哪里呢?
原来是躲藏在"/dev"目录下面。看到没有,它们分别指向内核的“/proc/self/fd/0(0/1/2)”处。至此,我挖到了它们,感觉真相大白。
5 验证一下“/dev/stdout”的真身
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#define WRITE_STRING "0123456789ABC\n"
#define LEN_OF_WRITE_STR strlen(WRITE_STRING)
int main(int argc,char *argv[])
{
size_t write_len=0 ;
write_len = write(2,WRITE_STRING,LEN_OF_WRITE_STR);
if (write_len != LEN_OF_WRITE_STR)
{
printf("File : /dev/stdout write error\n") ;
exit(1);
}
printf("Write %d bytes to /dev/stdout \n",(int)write_len) ;
return 0;
}
使用“gcc main.c -o stdout_test.out”编译成可执行程序:stdout_test.out。
我得到了以下验证:
(1)stdout的序号是全局的,且默认打开的,因为这里我没有open动作。
(2)系统API函数write,把字符串写到“/dev/stdout”,意味着输出在屏幕上了。
仔细看看,上面的代码没有“/dev/stdout”,为何说是成功了呢?如果再想进一步验证,可用以下代码来替代前几行。
fd = open("/dev/stdout",O_WRONLY);
if (fd < 0)
{
perror("perror: file open error ");
exit(1);
}
write_len = write(fd,WRITE_STRING,LEN_OF_WRITE_STR);
结果是一样的。