探讨shell命令中 >/dev/null 2>&1的实现原理

首先标准输入,标准输出,标准错误:
  • 标准输入是程序能够读取其输入的位置。缺省状况下,进程从键盘读取 stdin
  • 标准输出是程序写入其输出的位置。缺省状况下,进程将 stdout 写到终端屏幕上。
  • 标准错误是程序写入其错误消息的位置。缺省状况下,进程将 stderr 写到终端屏幕上。

为何有这三个很重要的概念呢?咱们知道,一个程序要运行,须要有输入、输出,若是出错,还要能表现出自身的错误。这是就要从某个地方读入数据、将数据输出到某个地方,出错了还要把错误给弄到一个地方去.这就够成了数据流(stream)。因此一般状况,每一个 Unix 程序在启动时都会打开三个流,一个用于输入,一个用于输出,一个用于打印诊断或错误消息。web

有了这三个概念.shell

再说说重定向:网络

数据流重导向(重定向)就是将某个指令(命令)执行后的执行返回值,通常这些返回值就是你执行完后出如今屏幕上那些结果数据,若是我不想让他默认流向屏幕.那么我能够把这些结果数据传输到其余的地方,例如文件或者装置(例如打印机,不过在Linux里面一切都一切都是文件,因此打印机这样的设备也是文件咯).这样数据就跑被我导向其余地方了.你懂的.因此东西都输出到屏幕,若是数据太多太乱.咱们也受不了啊.并且屏幕的terminal一关.东西就再也找不到了.若是我重定向到一个文件.这样就能够长期保存执行的日志了.spa

  • >数据流重导向:输出导向,会替换被导向的文件内容.
  • >>数据流重导向:输出导向,不会替换被导向的文件内容.会在屁股后面累加数据.

继续看看文件描述符:操作系统

维基百科,自由的百科全书上面是这样说的,文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者建立一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写每每会围绕着文件描述符展开。可是文件描述符这一律念每每只适用于UNIX、Linux这样的操做系统。文件描述符的优势主要有两个:基于文件描述符的I/O操做兼容POSIX标准。在UNIX、Linux的系统调用中,大量的系统调用都是依赖于文件描述符。看来这东西还真的有点抽象.也就是说若是程序不打开,文件孤单的在磁盘上面的时候是没有文件描述符的.能够想象一下.第一个打开的文件是0,第二个是1,依此类推。Unix 操做系统一般给每一个进程能打开的文件数量强加一个限制。更甚的是,unix 一般有一个系统级的限制。固然真是的状况是0,1,2通常已经被某些概念占用.再加上系统启动后已经不知道打开了多少文件.因此.咱们本身通常打开文件的时候描述符估计也已经到很大的数据了.可是这文件描述符缺点也是有的.好比完成的代码可读性也就会变得不好.你想啊.0,1,2....22231是知道是啥玩意儿?还好POSIX 定义了 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 来代替 0、一、2。这三个符号常量的定义位于头文件 unistd.h。文件描述符的有效范围是 0 到 OPEN_MAX。通常来讲,每一个进程最多能够打开 64 个文件(0 — 63)。对于 FreeBSD 5.2.一、Mac OS X 10.3 和 Solaris 9 来讲,每一个进程最多能够打开文件的多少取决于系统内存的大小,int 的大小,以及系统管理员设定的限制。Linux 2.4.22 强制规定最多不能超过 1,048,576 。.net

综合上面的基本概念:下面的也就不难理解了.命令行

标准输入 (stdin) :文件描述符为 0 ,使用 < 或 << ;(你不会非要写成0<或0<<吧.其实这个也没错.不过太累人了)其实能够理解为这个箭头指向哪里数据就往哪里跑.这里是输入(stdin).命令就经过<来获取数据.等于数据是从左边往命令里面流.设计

标准输出 (stdout):文件描述符为 1 ,使用 > 或 >> ;(你不会非要写成1>或1>>吧.其实这个也没错.不过太累人了)输出的时候当如不能用<或者<<,由于命令老是在前面嘛.这里命令要输出数据.因此数据的来源是命令,数据就会随着箭头指向你给的方向.unix

标准错误输出(stderr):文件描述符为 2 ,使用 2> 或 2>>;日志

再举例说明:

首先command >file 2>file 的意思是将命令所产生的标准输出信息,和错误的输出信息送到file中.command >file 2>file 这样的写法,stdout和stderr都直接送到file中, file会被打开两次,这样stdout和stderr会互相覆盖,这样写至关使用了两个同时去抢占file的管道.定向了2次.

那若是使用command >file 2>&1 这条命令就将stdout直接送向file, stderr 继承了第一次重定向(FD1)到管道后,再被送往file,此时,file 只被打开了一次,也只使用了一个管道FD1,它包括了stdout和stderr的内容.还能够这样理解.想是把file用管道接通了标准输出.而后把2表明的标准错误输出接到1表明的标准信息输出上面.就都通向了file了.

 从IO效率上,前一条命令的效率要比后面一条的命令效率要低,因此在编写shell脚本的时候,较多的时候咱们会用command > file 2>&1 这样的写法.

在看看一个实例(加深相关的理解,此实例引用网上博客.说是intel的笔试题):

问题:下面程序的输出是什么?(intel笔试2011)

int main(){
  fprintf(stdout,"Hello ");
  fprintf(stderr,"World!");
  return0;
}

而后发现输出是
World!Hello
而不是:
Hello World!

这是为何呢?在默认状况下,stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出,举例来讲就是printf(stdout, "xxxx") 和 printf(stdout, "xxxx\n"),前者会憋住,直到遇到新行才会一块儿输出。而printf(stderr, "xxxxx"),无论有么有\n,都输出.

最后:

看看什么叫/dev/null

UFO@UFO~:cd /dev
UFO@UFO :/dev$ls -l null
crw-rw-rw-  1 root root 1, 3 Feb 14  2012 null

看到了吧?是个字符设备文件(c).而这个东西呢?你能够叫他"黑洞", Blackhole?NO.不是天文学里面的黑洞.它很是等价于一个只写文件. 全部写入它的内容都会永远丢失. 而尝试从它那儿读取内容则什么也读不到. 然而, /dev/null 对命令行和脚本都很是的有用.

再来看看在glibc库的stdio.h头文件中:

#define    stdin    (&__sF[0])
#define    stdout    (&__sF[1])
#define    stderr    (&__sF[2])

好比

fprintf(stderr, "UFO\n");//那么将把"UFO"做为标准错误输出

在shell命令中,0,1和2分别对应glibc中的stdin,stdout和stderr,上面咱们已经大概了解到了:

  • 0 对应stdin     即标准输入
  • 1 对应stdout    即标准输出
  • 2 对应stderr    即标准错误输出

因此>/dev/null表示将程序经过printf或者fprintf打印到handle为1的stdout文件的信息,送到/dev/null空洞文件,/dev/null节点对应的kernel实现就是直接返回写入的字节数,因此程序认为成功存储到/dev/null了,可是>/dev/null这个操做不能将fprintf(stderr, "UFO\n")打印到stderr上的字符串送到>/dev/null下,因此必须使用2>&1命令,表示shell将送到2 stderr中的数据转送到1 stdout中,因此这样stderr中会显示到terminal上的信息也将被转送到/dev/null下了.

又看个实例吧:

luther@gliethttp:~$ cat a.c
#include <stdio.h>
int main(int argc, char *argv[])
{
    fprintf(stdout,"luther stdout\n");
    fprintf(stderr,"luther stderr\n");
    return 0;
}
luther@gliethttp:~$ gcc a.c
luther@gliethttp:~$ ./a.out
luther stdout
luther stderr
luther@gliethttp:~$ ./a.out >/dev/null
luther stderr//能够看到>/dev/null操做并不会将stderr信息送到/dev/null下

写一个test.sh脚本

luther@gliethttp:~$ chmod +x test.sh
luther@gliethttp:~$ cat test.sh
exec ./a.out >/dev/null
luther@gliethttp:~$ ./test.sh
luther stderr       //能够看到shell也不会将stderr信息送到/dev/null下
luther@gliethttp:~$ cat test.sh
exec ./a.out >/dev/null 2>&1
luther@gliethttp:~$ ./test.sh
luther@gliethttp:~$ //什么也没有输出,stderr信息被2>&1命令成功变为stdout信息,进而送入了/dev/null淹没
luther@gliethttp:~$ cat test.sh
exec ./a.out >/dev/null 1>&2
./test.sh
luther stdout
luther stderr
luther@gliethttp:~$

固然对于tee操做也一样存在如上问题,若是打印到stderr的log将不能被tee操做捕获,因此能够将stderr重定向到stdout来解决这个问题,

继续看看实例:

luther@gliethttp:~$ ./a.out
luther stdout
luther stderr
luther@gliethttp:~$ ./a.out|tee luther.txt
luther stdout
luther stderr
luther@gliethttp:~$ cat luther.txt
luther stdout
luther@gliethttp:~$ ./a.out 2>&1|tee luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ cat luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ ./a.out 1>&2|tee luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ cat luther.txt
luther@gliethttp:~$ //啥东西都没有,由于stdout被定向到stderr,因此全部log信息都不能被tee捕获
对于1>&2由于做为一个命令将被shell解析,因此放在哪里均可以,1>&2将影响到该组命令中全部的log输出,好比:
luther@gliethttp:~$ 1>&2 ./a.out|tee luther.txt
luther stderr
luther stdout
luther@gliethttp:~$ ./a.out 1>2 //只输出stderr
luther stderr
luther@gliethttp:~$ ./a.out 2>1 //只输出stdout
luther stdout
OK.不想写了.忙的很.上面的文章我只是添油加醋的把网络上不少由于大抄而散落各地的文章乱七八糟的整合了一下.因此我是站在巨人的肩上来了一次大抄.感谢那些巨人吧.其实上面提到的概念估计就这篇整合文章也不能彻底解释的很是完全.相关的概念更多的还在于实践和看文档再加本身的大脑过滤与思考.但愿和你们探讨!
相关文章
相关标签/搜索