一文搞懂Linux的标准输出/错误重定向

前言

 今天在写一个脚本时,需要将shell命令和可执行程序的输出重定向在某一个log文件中,但是遇到了点小问题,索性就研究下输出重定向到底怎么回事。

 Linux系统,有一个非常重要概念,就是一切皆文件。在使用shell脚本时,系统为了能够进行接收外部输入,同时向外部输出,将三个文件始终保持在打开的状态,并使用三个文件描述符0,1,2来分别指向这三个文件,以此来完成标准输入,标准输出,标准错误输出。

  • 标准输入:由键盘输入

  • 标准输出:输出到屏幕

  • 标准错误:输出到屏幕

 在正常情况下,我们执行shell命令时,其输出总是标准输出或者标准错误,因此总是会将输出的信息,不论是正常信息还是报错信息,都会打印在屏幕上,但有时我们不希望这些输出打印在屏幕上,而是希望这了信息能被保存到指定的文件中,这就是输出重定向。

 那么,究竟什么是标准输入/标准输出/标准错误呢?下面为大家一一介绍。

1.标准输出

 在终端输入

echo hello
hello

 终端会打印hello,但是我们echo出来的hello,到底去了什么地方?

 每个基于 Unix 的操作系统都有一个“输出的默认位置”的概念。由于这个短语比较啰嗦,所以大家都称它为“标准输出”或“stdout”,读作standard out。 shell(可能是 bash 或 zsh)一直在监视默认输出位置。当 shell 在标准输出那里看到新的输出时,它会将其打印在屏幕上,以便我们可以看到它。否则,shell不去监视标准输出位置,echo hello会将“hello”发送到那个默认位置,但我们却看不到。

2.标准输入

 标准输入(stdin)是命令监听信息的默认位置,尝试在终端输入cat,不添加任何参数

cat 
hello
hello
1234
1234

 不论输入什么,shell会将你的输入再次打印,shell怎么读取到你的输入呢,和标准输出类似,shell会一直监视默认输入位置,一有新的输入进来,shell便会把数据读进来,然后输出到stdout

3.标准错误

 标准错误(stderr)和stdin/stdout很像,区别就是stderr是错误信息存储的地方,例如, cat一个不存在的文件

cat ttt
cat: ttt: No such file or directory

 似乎和stdout没什么两样,但是我们借助管道来验证一下,在linux中,管道是将一个命令的stdout连接到另一个命令的stdin,可以使用管道符号|来完成这个操作,例如

echo "hello there"
hello there
echo "hello there" | sed "s/hello/hi/"
hi there

 这里的sed是将hello替换为hi,上面命令中,echo将hello there传输到标准输出,然后通过管道将hello there将其作为标准输入传递给sed,sed对其进行操作后,再输出到标准输出。

 那cat打印出的信息,到底是stdout呢还是stderr呢?看这个命令

cat ttt | sed "s/No such/hello world/"
cat: ttt: No such file or directory

  如果 cat ttt打印出来的信息是stdout的话,那No such会被替换成hello world,但似乎并没有被替换到,是的,cat ttt打印出的信息,是stderr而不是stdout。管道只会接收stdout,而不会接收stderr

4. 重定向

 这里我们终于可以知道什么是重定向了,默认情况下,我们shell执行的命令的输出一定会到stdout或者stderr,如果我们不想让信息输出到stdout或者stderr,那就要用到重定向了,我们可以使用>将输出进行重定向。

$ echo "hello world" > file
$ cat file
hello world

 这行命令做了两件事

  • file不存在,则创建
  • file存在,则用hello world覆盖其内容

 如果只是想追加内容而不是覆盖原有内容,则可以使用>>

$ echo "hello world" > file
$ cat file
hello world
$ echo "go go go" >>file
$ cat file
hello world
go go go

 其中,如果>或者>>前不添加文件描述符,则默认是将标准输出重定向到file,如果想重定向stderr,则需要表示为1 > file或者2 > file

5.文件描述符

 文件描述符(File descriptor)是表示输入/输出源的正整数,例如stdin是0,stdout是1,stderr是2,这些数字是由POSIX标准定义的,MacOS和Linux都实现了这个标准的一部分。

如果想将输出重定向到某一文件描述符,则需要借助>&运算符并跟上文件描述符来完成

# Redirect stdout to stdout (FD 1)
echo "hello there" >&1
hello there
# Redirect stdout to stderr (FD 2)
echo "hello there" >&2
hello there

 这和上面的输出重定向到某一文件基本一样,只不过重定向的最终目标变成了stdoutstderr,让我们继续通过管道看看这两个输出有什么区别

echo "no changes" >&1 | sed "s/no/some/"
some changes
# Redirect to stderr, so it does not come through
echo "no changes" >&2 | sed "s/no/some/"
no changes

 原理还是和上面一样,第一行将输出重定向到了标准输出,所以管道会将其传递给sed,第二行经echo的输出重定向到了标准错误,管道无法传递。

6.将stderr重定向到stdout

 如果一个脚本,将输入的参数的三个参数分别重定向到了stdout stderr stdout,那么一个命令就出现了两种不同的输出,使用管道时,就无法传递全部的输出作为下个命令的输入,我们编写一个这样的command

#!/bin/bash

for f in $@; do
  if [[ $f == "file2" ]]; then
    echo "stderr file2" >& 2
  else
    echo "stdout $f"
  fi
done
$ bash command file1 file2 file3
stdout file1
stderr file2
stdout file3

 用管道来处理,则只会处理部分,file2是stderr,无法被管道抓取。

$ bash command file1 file2 file3 | sed "s/^/Robot says: /"
stderr file2
Robot says: stdout file1
Robot says: stdout file3

 我们可以通过2>&1将标准错误重定向给标准输出,例如

cat ttt 2>&1 | sed "s/No such/hello world/"
cat: ttt: hello world file or directory

 由于stderr重定向到了stdout,管道就将输出的信息传递给了sed,sed做了处理并输出到stdout

同样的,对于command也可以这样做

dexu_tian@VM-4-10-ubuntu:~/Tmp/outputRedirect$ bash ./command file1 file2 file3 2>&1 | sed "s/std/Robot says: std/"
Robot says: stdout file1
Robot says: stderr file2
Robot says: stdout file3

7. 常用用法

 比较常见的用法是将一个命令的stdoutstderr都重定向到某一个文件中,那么它的写法就应该是这样的

cmd > logfile 2>&1

 cmd的stdout将会被重定向到logfile,stderr将会被重定向到stdout,由此实现了stderrstdout被重定向到了logfile.

 像2>&1一样,如果想重定向文件描述符,则需要表示为N >&M,其中N和M是文件描述符,其为1和2时,就是在重定向stdout和stdin了。如果M不是文件描述符,则使用文件名N>filename

8.输出静音

 我们可以通过这种重定向的语法,将所有的输出重定向到/dev/null, 它会吞下所有接收到的内容并且不做任何操作。

echo "hello there" >/dev/null

以上就是本文所要分享的内容,希望大家每天坚持进步~

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
)">
< <上一篇
下一篇>>