日期:2022年11月26日标签:Developer

Pipelines in Shell #

本篇文章内容需要读者知道 shell 的一些语法和作用,知道 shell 的用途,和一些基本的用法。

学习 shell 脚本必须要理解 pipeline 的概念,知道 command 的输入(input)和输出(output)的概念。只有掌握了 pipeline 的机制我们才能更好的写好 shell 脚本,本章内容详细介绍 pipeline。

shell 中的 command 可以接受一些输入然后产生一些输出,类似与数学中的函数表达式 y = f(x),输入参数 x,得到结果 y,command 就可以看作是一个函数方程。

标准输入、输出和错误 #

stdin、stdout、stderr

每一个程序都会接触到三个比较特殊的文件(linux 中所有东西都是文件):stdinstdoutstderr

  • stdin:standard input 的缩写,意思是标准输入,大部分程序从这里读取输入,用数字 0 表示
  • stdout:standard output 的缩写,意思是标准输出,大部分程序将输出信息写入到这个文件里,用数字 1 表示
  • stderr:standard error 的缩写,意思是标准错误,大部分程序出错了需要将错误信息写入这个文件,用数字 2 表示

上面用了 大部分 这个修饰词,意思并不是所有的程序都会按照上面的规范去读取和输出信息,因为任何程序都可以自由选择从哪里读取输入,将输出信息写入哪里。

这三个特殊的文件存储在 dev/ (dev 表示 device)文件夹下:

$ ls -al /dev/std*
lrwxrwxrwx 1 root root 15 Nov 26 20:25 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Nov 26 20:25 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Nov 26 20:25 /dev/stdout -> /proc/self/fd/1

每次在 shell 中运行一个程序,shell 会将键盘与程序的标准输入关联在一起,并将标准输出和标准错误与终端显示连接在一起:

stdin、stdout、stderr

从上图中我们可以看到数据的流转,键盘 -> stdin 文件 -> program -> stdout/stderr -> 屏幕,这里的数据流转本质上也是 pipeline。

pipe(|)操作符 #

可以通过 | 操作符将一个命令的输出重定向到另一个命令的输入,即将一个命令的 stdout 作为 另一个命令的 stdin。动手实践一下:

# 查看 file.txt 文件内容
$ cat file.txt
File
Edit
Selection
view
go
Run
Terminal
help
go
view

# 将文件中的内容排序并且去重
$ cat file.txt | sort | uniq
Edit
File
Run
Selection
Terminal
go
help
view

重新用途表示 cat test.txt | sort | uniq 的数据流转:

stdin、stdout、stderr

输入重定向 < #

< 可以将一个程序的标准输入重定向至某个文件,例如 rev < /dev/stdin,即 rev 命令将从标准输入读取输入,所以 rev < /dev/stdin回车 与 rev 直接回车命令是等价的。

输出重定向至文件 >>> #

  • > 将一个命令的输出写入一个文件,并覆盖(override)文件内容
  • >> 将一个命令的输出内容添加(append)至文件的尾部,不删除文件原有的内容
$ echo "Hello Shell" > test.txt
$ cat test.txt
Hello Shell
$ echo "Hello Pipeline" >> test.txt
$ cat test.txt
Hello Shell
Hello Pipeline

2>2>> 表示将标准错误重定向至某个文件,2 是标准错误的文件描述符

标准错误 #

我们试着在同一个路径下创建两个相同的目录,看看会发生什么:

$ mkdir js
$ mkdir js
mkdir: cannot create directory ‘js’: File exists

可以看到再次创建同名目录,shell 会报错,这里的错误信息就是标准错误,不是标准输出。我们可以试试使用 pipe 操作符,看能否重定向标准错误。

测试需要使用 tr(translate characters)命令,可以将字符小写转换为大写,例如:

$ echo 'Be quiet, this is a library!' | tr '[:lower:]' '[:upper:]'
BE QUIET, THIS IS A LIBRARY!

现在我们重定向我们的错误信息:

$ mkdir js | tr '[:lower:]' '[:upper:]'
mkdir: cannot create directory ‘js’: File exists

可以看到 tr 命令似乎并没有接收到任何标准输入,这是因为 mkdir js 出错了,错误信息输出到了标准错误,而 | 只会重定向标准输出。

在上面提到过 stdinstdoutstderr 都有一个文件描述符分别为 012

那如何处理标准错误信息呢?这里有一些常规的做法:

stdin、stdout、stderr

  • 2>&12> 用于标准错误重定向到某个文件,而 stdinstdoutstderr 是特殊的文件,所以这里表示标准错误(2)重定向到标准输出(1)
  • 2>./errors.txt:将标准错误信息重定向到某个文件,会覆盖文件原有内容
  • 2>/dev/null: 将标准错误输出到 /dev/null
  • 2>>./errors.txt: 将标准错误信息添加到某个文件
  • >output.txt 2>&1:将标准错误和标准输出都重定向到 output.txt 文件

我们试着将创建文件夹的报错信息重定向到 error.txt 文件:

$ mkdir js 2>error.txt
$ cat error.txt
mkdir: cannot create directory ‘js’: File exists

2>error.txt 表示重定向错误信息,所以第一次执行 mkdir js 时屏幕上并没有报错信息。

参考 #

推荐一本非常棒的 shell 学习教程:Effective Shell

(完)

目录