标准输入/输出(standard I/O)可能是软件设计原则里最重要的概念了。这个概念就是:程序应该有数据的来源端、数据的目的端(输出结果的地方)已经报告问题的地方,它们分别被称为标准输入(standard input)标准输出(standard output)以及标准错误输出(standard error)。程序不必知道也不用关心它的输入与输出背后是什么设备,当程序运行时,这些标准 IO 就已经打开并准备就绪了。

运行时的程序称为进程,在 Linux 系统中,对于每个进程来说,始终有 3 个"文件"处于打开状态:

  • stdin
  • stdout
  • stderr

这三个文件就是进程默认的标准输入、标准输出和标准错误输出。每个打开的文件都会被分配一个文件描述符。stdin,stdout 和 stderr 的文件描述符分别是 0,1 和 2(一个文件描述符说白了就是文件系统为了跟踪这个打开的文件而分配给它的一个数字)。对于 Bash 进程来说,stdin 默认就是键盘,stdout 和 stderr 默认都是屏幕。

我们可以把 IO 重定向简单理解为:
系统捕捉一个文件、命令、程序或脚本的输出,然后将这些输出作为输入发送到另一个文件、命令、程序或脚本中。

IO 重定向的基本概念

把标准输出重定向到文件
> 和 >> 符号把标准输出重定向到文件中
> 会覆盖掉已经存在的文件中的内容
>> 则把新的内容追加到已经存在的文件中的内容的尾部

比如下面的命令将会把文件 log.txt 变为一个空文件(就是 size 为 0):

$ : > log.txt

下面的命令则把 ls 命令输出的结果追加到 log.txt 文件的尾部:

$ ls >> log.txt 

其实这是省略了标准输出的文件描述符 1 的写法,完整的写法为:

$ ls > log.txt

把标准错误输出重定向到文件
标准错误的文件描述符为 2,所以重定向标准错误到文件的写法为:

$ ls > error.txt

如果想同时把标准输出和标准错误输出重定向到同一个文件中,可以使用下面的方法:

$ ls &> log.txt

还有另外一种写法为:

$ ls >log.txt >&

重定向到另一个文件描述符
比如通过这种方式把标准输出和标准错误输出重定向到同一个文件中:

$ ls >log.txt >&

注意:& 符号后面跟的是文件描述符而不是文件。

总结一下,我们大概可以得到下面两条规则:
M>N
# "M" 是一个文件描述符,如果没有明确指定的话默认为 1。
# "N" 是一个文件名。
# 文件描述符 "M" 被重定向到文件 "N"。

M>&N
# "M" 是一个文件描述符,如果没有明确指定的话默认为 1。
# "N" 是另一个文件描述符。

关闭文件描述符
文件描述符是可以关闭的,典型的写法有下面几种:
n<&-             # 关闭输入文件描述符 n
0<&-, <&-    # 关闭 stdin
n>&-             # 关闭输出文件描述符 n
1>&-, >&-    # 关闭 stdout

>/dev/null 2>&1 和 2>&1 >/dev/null

Linux 系统中有一个特殊的设备文件 /dev/null,发送给它的任何内容都会被丢弃,因此如果需要丢弃标准输出和标准错误输出的内容时可以使用下面的方法:
1>/dev/null 2>/dev/null
下面的写法与上面的写法是等同的,接下来我们重点来解释下面的写法:
>/dev/null 2>&1

>/dev/null
>/dev/null 的作用是将标准输出 1 重定向到 /dev/null 中,因此标准输出中的内容被完全丢弃了。

2>&1
2>&1 用到了重定向绑定,采用 & 可以将两个输出绑定在一起,也就是说错误输出将会和标准输出输出到同一个地方。

其实 Linux 在执行 shell 命令之前,就会确定好所有的输入输出位置,解释顺序为从左到右依次执行重定向操作。所以 >/dev/null 2>&1 的作用就是让标准输出重定向到 /dev/null 中,接下来因为错误输出的文件描述符 2 被重定向绑定到了标准输出的描述符 1,所以错误输出也被定向到了 /dev/null 中,错误输出同样也被丢弃了。

2>&1 >/dev/null
2>&1 >/dev/null 执行的结果和 >/dev/null 2>&1 是不一样的!它的执行过程为:

  1. 2>&1,将错误输出绑定到标准输出上。由于此时的标准输出是默认值,也就是输出到屏幕,所以错误输出会输出到屏幕。
  2. >/dev/null,将标准输出1重定向到 /dev/null 中。

exec 与重定向

exec <filename 命令会将 stdin 重定向到文件中。 从这句开始, 所有的 stdin 就都来自于这个文件了, 而不是标准输入(通常都是键盘输入)。 这样就提供了一种按行读取文件的方法。当然,也可以用这种方法来重定向标准输出和标准错误输出。下面的 upper.sh 程序把输入文件中的字母转换为大写并输出到另外一个文件中,脚本中使用 exec 同时重定向了 stdin 和 stdout:

#!/bin/bash

E_FILE_ACCESS=
E_WRONG_ARGS= if [ ! -r "$1" ] # 判断指定的输入文件是否可读
then
echo "Can't read from input file!"
echo "Usage: $0 input-file output-file"
exit $E_FILE_ACCESS
fi # 即使输入文件($)没被指定 if [ -z "$2" ]
then
echo "Need to specify output file."
echo "Usage: $0 input-file output-file"
exit $E_WRONG_ARGS
fi exec <& # 保存默认 stdin
exec < $ # 将会从输入文件中读取. exec >& # 保存默认 stdout
exec > $ # 将写到输出文件中.
# 假设输出文件是可写的 # -----------------------------------------------
cat - | tr a-z A-Z # 转换为大写
# 从 stdin 中读取
# 写到 stdout 上
# 然而,stdin 和 stdout 都被重定向了
# -----------------------------------------------
exec >& >&- # 恢复 stout
exec <& <&- # 恢复 stdin # 恢复之后,下边这行代码将会如预期的一样打印到 stdout 上
echo "File \"$1\" written to \"$2\" as uppercase conversion."
exit

在 upper.sh 程序所在的目录下创建 in.txt 和 out.txt 文件,编辑 in.txt 文件的内容为:

abcd
xxyy

然后运行 upper.sh 程序:

$ ./upper.sh in.txt out.txt

其实还有一种稍微简单一些的方式把标准输入和标准输出重定向到文件:
[j]<>myfile
# 为了读写 myfile,把文件 myfile 打开, 并且将文件描述符 j 分配给它。
# 如果文件 myfile 不存在, 那么就创建它。
# 如果文件描述符 j 没指定,那默认是 fd 0,即标准输入。
这种应用通常是为了把内容写到一个文件中指定的地方,比如下面的 demo:

echo  > myfile # 写字符串到 myfile
exec <> myfile # 打开 myfile 并且将 fd 分配给它
read -n <& # 只读取4个字符
echo -n . >& # 写一个小数点
exec >&- # 关闭 fd
cat myfile # ==> 1234.67890

避免子 shell
默认情况下,不能在子 shell 中改变父 shell 中变量的值。下面的 demo 通过 exec 重定向 IO 有效的规避了子 shell 问题:

#!/bin/bash

E_WRONG_ARGS=
if [ -z "$1" ]
then
echo "Usage: $0 input-file"
exit $E_WRONG_ARGS
fi Lines= cat "$1" | while read line; # 管道会产生子 shell
do {
echo $line
(( Lines++ )); # 增加这个变量的值
# 但是外部循环却不能访问
}
done echo "Number of lines read = $Lines" #
# 错误!
echo "------------------------"
exec <> "$1"
while read line <&
do {
echo "$line"
(( Lines++ )); # 增加这个变量的值
# 现在外部循环就可以访问了
# 没有子shell, 现在就没问题了
}
done
exec >&-
echo "Number of lines read = $Lines"
exit

把上面的代码保存到 avoid-subshell.sh 文件中,然后运行下面的命令:

$ ./avoid-subshell.sh in.txt

使用重定向 IO 的方式获得了正确的结果。

代码块重定向

像 while、until、for 和 if/then 等代码块也是可以进行 IO 重定向的,下面的 demo 把 while 循环的标准输入重定向到一个文件:

#!/bin/bash

E_WRONG_ARGS=
if [ -z "$1" ]
then
echo "Usage: $0 input-file"
exit $E_WRONG_ARGS
fi count= while [ "$name" != xxyy ]
do
read name
if [ -z "$name" ]; then
break
fi
echo $name
let "count += 1"
done <"$1"
echo "$count names read"
exit

把上面的代码保存到文件 whileblock.sh 中,然后执行下面的命令,可以看到标准输入重定向后的结果:

$ ./whileblock.sh in.txt

下面的 demo 则同时重定向了 for 循环的标准输入和标准输出:

#!/bin/bash

E_WRONG_ARGS=
if [ -z "$1" ]
then
echo "Usage: $0 input-file output-file"
exit $E_WRONG_ARGS
fi if [ -z "$2" ]
then
echo "Usage: $0 input-file output-file"
exit $E_WRONG_ARGS
fi FinalName="xxyy"
line_count=$(wc "$1" | awk '{ print $1 }') for name in $(seq $line_count)
do
read name
echo "$name"
if [ "$name" = "$FinalName" ]
then
break
fi
done < "$1" > "$2"
exit

把上面的代码保存到文件 forblock.sh 中,然后执行下面的命令,终端上没有任何输出,因为输出都被重定向到了 out.txt 文件:

$ ./forblock.sh in.txt out.txt

总结

在众多的场景中,IO 重定向的结果是显而易见的。但在一些特殊的用例中结果就不是那么明显了,需要我们理解重定向的基本规则,才能理解哪些特殊写法的含义或是写出我们自己满意的脚本。

参考:
《高级 Bash 脚本编程》
shell 中的>/dev/null 2>&1 是什么鬼?

Bash : IO 重定向的更多相关文章

  1. Perl IO:IO重定向

    文件句柄和文件描述符的关系 文件描述符是操作系统的资源,对于实体文件来说,每打开一次文件,操作系统都会为该进程分配一个文件描述符来关联(指向)这个文件,以后操作文件数据都根据这个文件描述符来操作,而不 ...

  2. linux学习14 Linux运维高级系统应用-glob通配及IO重定向

    一.回顾 1.bash基础特性:命令补全,路径补全,命令引用 2.文件或目录的复制,移动及删除操作 3.变量:变量类型 存储格式,数据表示范围,参与运算 二.bash的基础特性 1.globbing: ...

  3. 四、IO重定向和管道以及基本文本处理工具

    一.三种IO设备 程序:数据+指令 或 数据结构+算法程序必须能够读入输入然后经过加工来产生结果,其接受的输入可以是变量.数组.列表.文件等等,生产出来的结果可以使变量.数组.列表.文件等等.即:程序 ...

  4. shell IO重定向

    I/O重定向 默认情况下,有3个"文件"处于打开状态,stdin,stdout,stderr:重定向的解释:捕捉一个文件,命令,程序,脚本或者脚本中的代码块的输出,然后将这些输出作 ...

  5. [C++]竞赛模板·数据统计与IO(重定向版与非重定向版)

      /* 数据统计与IO 重定向版模板 描述:本机测试用文件数据流重定向,一旦提交到比赛就自动“删除”重定向语句 */ # define LOCAL #include<stdio.h> # ...

  6. IO重定向与管道

    一.三种IO设备 程序:数据+指令 或 数据结构+算法 程序必须能够读入输入然后经过加工来产生结果,其接受的输入可以是变量.数组.列表.文件等等,生产出来的结果可以使变量.数组.列表.文件等等.即: ...

  7. 4-3 管理及IO重定向

    1. 系统设定默认输出设备:标准输出(STDOUT,1) 系统设定默认输入设备:标准输入(STDIN,0) 系统设定默认错误设备:标准错误(STDERR,2) 2. 标准输入:键盘 标准输出和错误输出 ...

  8. <实训|第十一天>学习一下linux中的进程,文件查找,文件压缩与IO重定向

    [root@localhost~]#序言 在今后的工作中,运维工程师每天的例行事务就是使用free -m,top,uptime,df -h...每天都要检查一下服务器,看看是否出现异常.那么今天我们就 ...

  9. bash shell:重定向标准错误输出

    如何重定向标准错误输出到标准输出?如何把标准错误输出输出到一个文件? Bash提供了I/O重定向工具,有3个缺省的文件(标准输出流): stdin - 用来获取输入,比如键盘.文件重定向 stdout ...

随机推荐

  1. windows 自动贴边

    设置windows 自动贴边(窗口拖到显示器边框就自动适应屏幕): https://jingyan.baidu.com/article/d2b1d1029d17b95c7e37d4f0.html 资源 ...

  2. 分布式文件系统(HDFS)与 linux系统文件系统 对比

    初次接触分布式文件系统,有很多迷惑.通过参考网络文章,这里进行对比一下Hadoop 分布式文件系统(HDFS)与 传统文件系统之间的关系:   Linux 文件系统 分布式文件系统 块 块对应物理磁盘 ...

  3. 用Python做股市数据分析(一)

    本文由 伯乐在线 - 小米云豆粥 翻译.未经许可,禁止转载!英文出处:Curtis Miller.欢迎加入翻译组. 这篇博文是用Python分析股市数据系列两部中的第一部,内容基于我犹他大学 数学39 ...

  4. Centos7下安装与卸载docker应用容器引擎

    Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发布到任何流行的 Li ...

  5. HashMap探究

    HashMap 前置 //初始化容量 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //容器最大容量 static final i ...

  6. OpenLdap 对接内部系统(Gitlab+Wiki+Jumpserver+Openvpn)配置

    LDAP 全称轻量级目录访问协议(英文:Lightweight Directory Access Protocol),是一个运行在 TCP/IP 上的目录访问协议.目录是一个特殊的数据库,它的数据经常 ...

  7. 4.8 Sublime Text3 中配置 Python环境 --之下Sublime配置Python环境

    返回总目录 目录: 1.没有配置之前 2.安装Package Control插件 3.安装其他库: 4.配置其他操作: (一)没有配置之前: 我们试着运行以下,会效果怎么样? 1.首先选择Python ...

  8. C#中删除集合中符合条件的元素以及需注意属相

    如果用foreach,会造成被遍历的集合更改后带来异常问题. 此时,用for循环可有效的解决这个问题. for(int i=0;i<List.Count;i++) { if(条件是真) { Li ...

  9. MySql常用命令集Mysql常用命令showdatabases;显示数据库createdatab

    MySql 常用命令集 Mysql常用命令 show databases; 显示数据库 create database name; 创建数据库 use databasename; 选择数据库 drop ...

  10. day15 Python全局变量和局部变量

    在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量. 全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序. 当全局变量与局部变量同名时: 在定义局部变量的子程序内,局部变 ...