Bash : IO 重定向

标准输入/输出(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 1>log.txt

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

$ ls 2>error.txt

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

$ ls &>log.txt

还有另外一种写法为:

$ ls >log.txt 2>&1

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

$ ls >log.txt 2>&1

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

总结一下,我们大概可以得到下面两条规则:
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=70
E_WRONG_ARGS=71

if [ ! -r "$1" ] # 判断指定的输入文件是否可读
then
    echo "Can‘t read from input file!"
    echo "Usage: $0 input-file output-file"
    exit $E_FILE_ACCESS
fi #  即使输入文件($1)没被指定

if [ -z "$2" ]
then
    echo "Need to specify output file."
    echo "Usage: $0 input-file output-file"
    exit $E_WRONG_ARGS
fi

exec 4<&0 # 保存默认 stdin
exec < $1 # 将会从输入文件中读取.

exec 7>&1 # 保存默认 stdout
exec > $2 # 将写到输出文件中.
# 假设输出文件是可写的

# -----------------------------------------------
cat - | tr a-z A-Z # 转换为大写
# 从 stdin 中读取
# 写到 stdout 上
# 然而,stdin 和 stdout 都被重定向了
# -----------------------------------------------
exec 1>&7 7>&- # 恢复 stout
exec 0<&4 4<&- # 恢复 stdin

# 恢复之后,下边这行代码将会如预期的一样打印到 stdout 上
echo "File \"$1\" written to \"$2\" as uppercase conversion."
exit 0

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

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

#!/bin/bash

E_WRONG_ARGS=71
if [ -z "$1" ]
then
    echo "Usage: $0 input-file"
    exit $E_WRONG_ARGS
fi

Lines=0

cat "$1" | while read line; # 管道会产生子 shell
do {
    echo $line
    (( Lines++ )); #  增加这个变量的值
    # 但是外部循环却不能访问
}
done

echo "Number of lines read = $Lines" # 0
# 错误!
echo "------------------------"
exec 3<> "$1"
while read line <&3
do {
    echo "$line"
    (( Lines++ )); #  增加这个变量的值
    # 现在外部循环就可以访问了
    #  没有子shell, 现在就没问题了
}
done
exec 3>&-
echo "Number of lines read = $Lines"
exit 0

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

$ ./avoid-subshell.sh in.txt

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

代码块重定向

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

#!/bin/bash

E_WRONG_ARGS=71
if [ -z "$1" ]
then
    echo "Usage: $0 input-file"
    exit $E_WRONG_ARGS
fi

count=0

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

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

$ ./whileblock.sh in.txt

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

#!/bin/bash

E_WRONG_ARGS=71
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 0

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

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

总结

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

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

原文地址:https://www.cnblogs.com/sparkdev/p/10247187.html

时间: 2024-10-29 19:11:39

Bash : IO 重定向的相关文章

bash的基本特性之文件名通配 及IO重定向,管道详解

本篇博文依然涉及到bash的基本特性 文件名通配,并讲解IO重定向以及管道的使用 管道配合tr命令的应用 bash的基础特性之一:文件名通配 globbing:文件名通配(整体文件名匹配,而非部分) 匹配模式:元字符 *:匹配任意长度的任意字符 pa*,*pa*,*pa,*p*a* ?:匹配任意单个字符 pa?,??pa,p?a,p?a? []:匹配指定范围内的任意单个字符 有几种特殊字符: [a-z],[A-Z],[0-9],[a-z0-9] [[:upper:]]:所有大写字母 [[:low

Linux基础入门--IO重定向及管道

IO重定向及管道 一直都提到,程序:指令+数据 其实程序也有IO,数据的来源有多个地方:文件.外部 可用于输入的设备:文件(linux一切皆文件) 键盘设备.文件系统上的常规文件加载内容.网卡等: 可用于输出的设备:文件(linux一切皆文件) 显示器.文件系统上的常规文件输出保存.网卡等: 程序的数据流有三种: 输入的数据流:<-- 标准输入(stdin),是键盘: 输出的数据流:--> 标准输出(stdout),显示器: 错误输出流:  --> 错误输出(stderr),显示器: f

class-5 &nbsp; IO重定向(二)及用户组(一)

一.IO重定向(二) 1.tr 命令 tr –  转换和删除字符 tr [OPTION]... SET1 [SET2] 选项: -c 或--complerment :取字符集的补集 [[email protected] ~]#  tr -c 'abc' '1' << end > abcdefghijklmn > end abc111111111111[[email protected] ~]# -d 或--delete :删除所有属于第一字符集的字符(可以用八进位制数代替): [[

Linux基础之IO重定向及管道详解(涉及tr、tee命令)

我在刚开始接触IO重定向时,经历了由懂到不懂然后了然的过程,当然现在的我也不可能说对于IO重定向就彻底懂了,只是在解决一些问题或实现某些结果时不会因为IO重定向的问题而迷惑了.     什么叫IO重定向? 为了解释这个问题,我们要先明白什么叫IO,什么叫做程序的数据流. 什么叫IO? 在Linux或计算机领域IO指的是信息的输入和输出. 在Linux中可用于输入的设备:文件(Linux中一切皆文件) 具体包括:键盘设备.文件系统上的常规文件.网卡等. 在Linux中用于输出的设备:文件 具体包括

[Linux 005]——IO重定向

通常在 Shell 中执行命令的时候,我们会在输入命令的下方看到执行结果,操作系统默认将命令的执行结果输出到显示器上.当然,我们也可以手动的指定输出路径,或者输入路径,这就是 I/O 重定向. 1.标准输出重定向 使用 cat 命令,命令的执行结果将会打印在屏幕中. 我们使用 > 来进行输出重定向,此时屏幕上不再打印命令执行结果了,而是将执行结果保存到了 ./target.xxx 文件中. 来看一下 ./target.xxx 文件中的内容: 2.标准输入重定向 tr 命令可以从键盘上读取标准输入

SHELL——通配符,IO重定向,正则表达式

一.正则表达式: 元字符是用来阐释字符表达式意义的字符,简言之,就是用来描述字符的字符. 正则表达式RE(Regular Expression)是由一串字符和元字符构成的字符串. 正则表达式的主要功能是文本查询和字符串操作,它可以匹配文本的一个字符或字符集合.实际上正则表达式完成了数据的过滤,将不满足正则表达式定义的数据拒绝掉,剩下与正则表达式匹配的数据. 1.正则表达式的组成 (1).一般字符:没有特殊意义的字符 (2).特殊字符(meta字符):元字符,有在正则表达式中有特殊意义 2.如下讲

IO重定向与文件描述符

1.介绍 IO重定向用于捕捉一个文件,命令,程序,脚本或者代码块的输出,然后把捕捉到的输出作为输入发送给另外一个文件,命令,程序或脚本. 终端程序一般从单一源以流的形式聚集输入和显示输出,script执行时(进程), 系统会默认开启3个标准文件,stdin, stdout,stderr . script默认会由stdin读取数据,默认指键盘,由stdout输出执行结果,默认指屏幕; 若有错误发生,则由stderr显示信息,默认也指向屏幕.系统开启这3个文件时,以文件代码(分别为0,1,2)作为连

Linux管道及IO重定向小练习

1.统计/usr/bin目录下的文件个数 [[email protected] ~]# ls /usr/bin | wc -l 1306 [[email protected] ~]# 2.取出当前系统上所有用户的SHELL,要求,每种SHELL只显示一次,并且按顺序进行显示 [[email protected] ~]# cut -d: -f7 /etc/passwd | sort -u /bin/bash /bin/nologin /bin/sync /bin/tcsh /sbin/halt /

IO重定向&管道&元字符

一.glob及IO重定向 bash基础特性:命令补全,路径补全,命令引用 1.bash的其他特性 globbing:文件名统配 动态角度:运行中的内核,在内核之上,由内核支配负责管控的文件系统 静态:内核自己其实也是文件系统上的一个文件而已 2.元字符 匹配模式:元字符: *:匹配任意长度的任意字符 *pa* 包含pa pa*  pa开头 *pa pa结尾, ?:匹配任意单个字符 []:匹配指定范围内的任意单个字符 有几种特殊格式: [a-z],[A-Z],[0-9],[a-z0-9] [abc