linux shell脚本编程笔记(五): 重定向

时间:2022-05-01 23:32:23

I/O重定向

简述:

默认情况下始终有3个"文件"处于打开状态, stdin (键盘), stdout (屏幕), and stderr (错误消息输出到屏幕上). 这3个文件和其他打开的文件都可以被重定向. 对于重定向简单的解释就是捕捉一个文件, 命令, 程序, 脚本, 或者甚至是脚本中的代码块的输出, 然后将这些输出作为输入发送到另一个文件, 命令, 程序, 或脚本中.

每个打开的文件都会被分配一个文件描述符.stdin, stdout, 和stderr的文件描述符分别是0, 1, 和 2. 对于正在打开的额外文件, 保留了描述符3到9. 在某些时候将这些格外的文件描述符分配给stdin, stdout, 或者是stderr作为临时的副本链接是非常有用的. 在经过复杂的重定向和刷新之后需要把它们恢复成正常的样子.

文件重定向通常的用法:

       COMMAND_OUTPUT >
# 重定向stdout到一个文件.
# 如果没有这个文件就创建, 否则就覆盖. ls -lR > dir-tree.list
# 创建一个包含目录树列表的文件. : > filename
# > 会把文件"filename"截断为0长度.
# 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
# : 是一个占位符, 不产生任何输出. > filename
# > 会把文件"filename"截断为0长度.
# 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
# (与上边的": >"效果相同, 但是在某些shell下可能不能工作.) COMMAND_OUTPUT >>
# 重定向stdout到一个文件.
# 如果文件不存在, 那么就创建它, 如果存在, 那么就追加到文件后边. # 单行重定向命令(只会影响它们所在的行):
# -------------------------------------------------------------------- >filename
# 重定向stdout到文件"filename".
>>filename
# 重定向并追加stdout到文件"filename".
>filename
# 重定向stderr到文件"filename".
>>filename
# 重定向并追加stderr到文件"filename".
&>filename
# 将stdout和stderr都重定向到文件"filename". #==============================================================================
# 重定向stdout, 一次一行.
LOGFILE=script.log echo "This statement is sent to the log file, \"$LOGFILE\"." >$LOGFILE
echo "This statement is appended to \"$LOGFILE\"." >>$LOGFILE
echo "This statement is also appended to \"$LOGFILE\"." >>$LOGFILE
echo "This statement is echoed to stdout, and will not appear in \"$LOGFILE\"."
# 每行过后, 这些重定向命令会自动"reset". # 重定向stderr, 一次一行.
ERRORFILE=script.errors bad_command1 >$ERRORFILE # 错误消息发到$ERRORFILE中.
bad_command2 >>$ERRORFILE # 错误消息添加到$ERRORFILE中.
bad_command3 # 错误消息echo到stderr,
#+ 并且不出现在$ERRORFILE中.
# 每行过后, 这些重定向命令也会自动"reset".
#============================================================================== >&
# 重定向stderr到stdout.
# 得到的错误消息与stdout一样, 发送到一个地方. i>&j
# 重定向文件描述符i 到 j.
# 指向i文件的所有输出都发送到j中去. >&j
# 默认的, 重定向文件描述符1(stdout)到 j.
# 所有传递到stdout的输出都送到j中去. < FILENAME
< FILENAME
# 从文件中接受输入.
# 与">"是成对命令, 并且通常都是结合使用.
#
# grep search-word <filename [j]<>filename
# 为了读写"filename", 把文件"filename"打开, 并且分配文件描述符"j"给它.
# 如果文件"filename"不存在, 那么就创建它.
# 如果文件描述符"j"没指定, 那默认是fd , stdin.
#
# 这种应用通常是为了写到一个文件中指定的地方.
echo > File # 写字符串到"File".
exec <> File # 打开"File"并且给它分配fd .
read -n <& # 只读4个字符.
echo -n . >& # 写一个小数点.
exec >&- # 关闭fd .
cat File # ==> 1234.67890
# 随机存储. |
# 管道.
# 通用目的的处理和命令链工具.
# 与">"很相似, 但是实际上更通用.
# 对于想将命令, 脚本, 文件和程序串连起来的时候很有用.
cat *.txt | sort | uniq > result-file
# 对所有的.txt文件的输出进行排序, 并且删除重复行,
# 最后将结果保存到"result-file"中.

将输入输出重定向与管道相结合

    command < input-file > output-file

    command1 | command2 | command3 > output-file

将多个输出流重定向到一个文件上:

    ls -yz >> command.log >&
# 将错误选项"yz"的结果放到文件"command.log"中.
# 因为stderr被重定向到这个文件中,
#+ 所有的错误消息也就都指向那里了. # 注意, 下边这个例子就不会给出相同的结果.
ls -yz >& >> command.log
# 输出一个错误消息, 但是并不写到文件中. # 如果将stdout和stderr都重定向,
#+ 命令的顺序会有些不同.

关闭文件描述符

n<&-
#关闭输入文件描述符n. <&-
<&-
#关闭stdin. n>&-
#关闭输出文件描述符n. >&-
>&-
#关闭stdout.

子进程继承了打开的文件描述符. 这就是为什么管道可以工作. 如果想阻止fd被继承, 那么可以关掉它:

    # 只重定向stderr到一个管道.

    exec >&                              # 保存当前stdout的"值".
ls -l >& >& >&- | grep bad >&- # 对'grep'关闭fd (但不关闭'ls').
# ^^^^ ^^^^
exec >&- # 现在对于剩余的脚本关闭它.

exec

exec 命令会将stdin重定向到文件中. 从这句开始, 后边的输入就都来自于这个文件了, 而不是标准输入了(通常都是键盘输入). 这样就提供了一种按行读取文件的方法, 并且可以使用sed 和/或 awk来对每一行进行分析.
使用exec重定向标准输入:

    #!/bin/bash
# 使用'exec'重定向标准输入. exec <& # 将文件描述符#6与stdin链接起来.
# 保存了stdin. exec < data-file # stdin被文件"data-file"所代替. read a1 # 读取文件"data-file"的第一行.
read a2 # 读取文件"data-file"的第二行. echo
echo "Following lines read from file."
echo "-------------------------------"
echo $a1
echo $a2 echo; echo; echo exec <& <&-
# 现在将stdin从fd #6中恢复, 因为刚才我们把stdin重定向到#6了,
#+ 然后关闭fd # ( <&- ), 好让这个描述符继续被其他进程所使用.
#
# <& <&- 这么做也可以. echo -n "Enter data "
read b1 # 现在"read"已经恢复正常了, 就是从stdin中读取.
echo "Input read from stdin."
echo "----------------------"
echo "b1 = $b1" echo exit

例如可以这样读文件:

exec <>test.sh;
#打开test.sh可读写操作,与文件描述符3绑定 while read line<&
do
echo $line;
done
#循环读取文件描述符3(读取的是test.sh内容)
exec >&-
exec <&-
#关闭文件的,输入,输出绑定

当然一般我们可以直接重定向文件到一个代码块,相当于重定向到这个代码块的标准输入。

重定向到代码块

通常我们可以将利用将代码块的输入输出重定向的方法实现一些对于文件的操作。

例如:

# while的重定向
while [ "$name" != Smith ]
do
read name
echo $name
done <"$Filename" # 以上结构也可以这样写
exec <& # 把标准输入绑定到文件描述符3,以便稍后恢复
exec <"$Filename" # 重定向标准输入为这个文件 while [ "$name" != Smith ]
do
read name
echo $name
done exec <& # 恢复标准输入
exec <&- # 关闭文件描述符3以供后续使用 # until的重定向
until [ "$name" = Smith ]
do
read name
echo "$name"
done <"$Filename" # for循环的重定向
lineCount=`wc $Filename | awk '{print $1}'` # 统计文件的行数 for name in `seq $lineCount`
do
read name
echo $name
if [ "$name" = Smith ] # 循环到Smith的时候就退出循环
then
break
fi
done <"$Filename" # if/then结构的重定向
if :
then
read name
echo "$name"
fi <"$Filename" # 这个重定向只读了文件中的一行

Here Documents

here documents是一段代码块,可以利用重定向将这个代码块传递到一个交互或者命令中。

标准的结构为:

COMMAND <<HERESTRING
...
HERESTRING

例如:

tail << HERE
a
b
HERE

输出为:

a
b

也可以用<<-来排除tab对heredocuments的影响

例如:

cat <<- HERE
a
b
HERE

输出会是

a
b

此外,here document可以使用参数替换

例如:

name="Smith"
cat << HERE
a
$name
HERE

将会输出

a
Smith

参数替换可以使用''来禁用

name="Smith"
cat << 'HERE'
a
$Smith
HERE

将会输出

a
$Smith

可以利用这种方法来产生代码,不会造成问题。

函数也可以使用here document来提供数据

function echoabc(){
read line1
echo line1
read line2
echo line2
} echoabc << HERE
a
b
HERE

也可以输出

a
b

还可以使用here document注释代码块

例如:

: << HERE
echo a
HERE echo b

输出为:

b

相当于将here document传递给了:,不会有任何的影响。

注意here document的结束符前后都不可以有空格,否则会产生识别上的错误

here string

here string可以认为是here document的一种形式。

例如:

HERE="abcd"
tail <<< $HERE

将会输出:

abcd

相当于直接将一个string传递给了命令.

一个小陷阱:

echo "some text here" > file
cat < file > file
cat < file
sed 's/hello/hi/' file > file

上面这两个例子都会清空文件file。原因同一个:在 IO Redirection 中,stdout 与 stderr 的管道会先准备好,才会从 stdin 取资料。故而上面的两个例子都会先执行"> file"操作,即清空了文件,再执行前面的操作。这个陷阱需要额外注意。

参考资料:

https://www.jianshu.com/p/681a3a762fe5

http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=218853&page=7#pid1636825