1. IO概念
程序是由指令+数据组成的。换言之 , 程序是对读入的数据进行处理 , 再输出数据。数据的输入(Input) , 输出(Output) , 简称为IO , 在没有指定输入输出的情况下 , 默认为标准输入和标准输出。
linux系统将每个对象当作文件处理 , 这包括输入和输出进程。linux用文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数 , 可以唯一标识会话中打开的文件。每个进程一次最多可以有九个文件描述符。出于特殊目的,bash shell保留了前三个文件描述符(0,1和2)
Linux给程序提供三种IO设备
标准输入(
STDIN) :0。默认接受来自键盘的输入标准输出(
STDOUT) :1。 默认输出到终端窗口标准错误(
STDERR) :2。 默认输出到终端窗口
[root@centos7 ~]# ll /dev/std*
lrwxrwxrwx 1 root root 15 Aug 23 15:15 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root 15 Aug 23 15:15 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root 15 Aug 23 15:15 /dev/stdout -> /proc/self/fd/1默认情况下,上述的块设备为默认的输入输出设备
在Linux中 , 一切皆文件 ,我们每打开文件 , 系统都会自动分配一个FD ( file description , 文件描述符 ) 。上面的0,1,2就是系统分配的文件描述符。
ll /proc/$$/fd 查看目前的文件描述符
exec 8<>/data/hosts 表示给/data/hosts文件指定一个文件描述符8,且8与/data/hosts之间是软链接
exec 8>&- 删除8号这个文件描述符[root@centos7 ~]# ll /proc/$$/fd
total 0
lrwx------ 1 root root 64 Aug 23 21:55 0 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 1 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 2 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 255 -> /dev/pts/0
[root@centos7 ~]# cp /etc/hosts /tmp/
[root@centos7 ~]# exec 8<> /tmp/hosts
[root@centos7 ~]# ll /proc/$$/fd
total 0
lrwx------ 1 root root 64 Aug 23 21:55 0 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 1 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 2 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 255 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:58 8 -> /tmp/hosts
[root@centos7 ~]# cat /proc/$$/fd/8
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
[root@centos7 ~]# exec 8>&-
[root@centos7 ~]# ll /proc/$$/fd
total 0
lrwx------ 1 root root 64 Aug 23 21:55 0 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 1 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 2 -> /dev/pts/0
lrwx------ 1 root root 64 Aug 23 21:55 255 -> /dev/pts/0
2. IO重定向
2.1 重定向基础用法
Linux可以通过特定的符号改变进程的默认IO设备。>是输出重定向符号 , <是输入重定向符号。
/dev/stdin、/dev/stdout、/dev/stderr等设备只是数据默认的流向目标(严格地说是文件描述符0、1、2的输出目标) , 它们不等价于"标准输入0、标准输出1、标准错误2"。之所以称为重定向 , 就是将数据的流向改变 , 不再输入到这些默认设备中。
Shell 文件描述符操作方法一览表
根据上面表格所说的文件描述符操作方法可以归纳了以下输入输出用法
() :合并多个程序的STDOUT。同样()有一个特殊的用法:(COMMAND 2>&1) >> /path/to/file.out,主要的效果与command >>file 2>&1一样,均是将标准输出内容与标准错误输出内容均追加至文件中。
(cal 2007;cal 2008) > all.txt重定向的顺序很重要。 ls / >file1 2>&1 : 表示先打开file1作为标准输出(fd=1)的目的地 , 然后再将标准错误绑定到标准输出(已经是file1)上 , 这样无论是标准错误还是标准输出都重定向到file1中。它等价于ls / &>file1 。&符号表示描述符重用(fd 2 duplicate from fd 1)。将其理解为文件描述符2复制了文件描述符1 , 或者文件描述2重用文件描述符1 , 使得fd=2也指向了fd=1所指向的文件。
而ls / 2>&1 >file1 表示先将标准错误指向到标准输出 , 此时标准输出还是/dev/sdtout(即屏幕) , 因此标准错误的输出目标是/dev/stdout(屏幕)。之后再打开file1作为标准输出的目标。因此 , 它最终将标准错误重定向到/dev/stdout , 将标准输出重定向到file1。可以让ls命令产生错误来测试 , ls dlfjasl 2>&1 >file1 ,结果将直接显示在屏幕上。
2.2 高级用法
将stdout或stderr丢到/dev/null表示丢弃输出信息 , 反过来 , 将/dev/null重定向到某个文件则表示清空文件。
[root@centos7 ~]# cat /dev/null > ab.sh除此 , 还有以下几种方法快速清空文件
[root@centos7 ~]# > ab.sh
[root@centos7 ~]# : > ab.sh # 或"true >ab.sh" , 其实它们都等价于">ab.sh"
[root@centos7 ~]# echo '' > ab.sh
[root@centos7 ~]# truncate -s 0 ab.sh # truncate命令用于收缩和扩展文件大小
[root@centos7 ~]# dd if=/dev/null of=ab.sh> bigfile 效果是创建了一个bigfile的空文件。背后的原理是利用重定向标准输出的原理 , 重定向输出至bigfile文件 , 由于无任何输出结果 , 所以直接效果就是创建了一个空的bigfile文件。所以一个比较安全的创建空文件的方法是:>> file
原因:
假如file文件原来就存在 , 那么>> 不会覆盖原来的文件 , 只会在原文件基础上累加。
>>不会刷新时间 ,touch命令会刷新时间。假如
abc_link为abc的软连接。那么我们>abc_link时,会直接覆盖源文件abc
在有输出类的重定向(包括错误重定向)语句中 , 命令执行之前就已经将文件截断为0大小。所以如果正在编辑一个文件并将编辑的结果重定向回这个文件将出现异常 , 因为截断后就没有合适的内容用于编辑。
[root@centos7 ~]# head a.log > a.log有些时候直接使用>覆盖输出是比较危险的。可以使用set -C来设置如果输出重定向文件已经存在则不覆盖。使用set +C来取消set -C的效果。如果在设置了set -C时仍然想强制覆盖 , 可以使用>|代替>来重定向输出。同理错误输出也有此特性。
[root@centos7 tmp]# set -C
[root@centos7 tmp]# cat flip >ttt.txt
-bash: ttt.txt: cannot overwrite existing file
[root@centos7 tmp]# cat flip >| ttt.txt
[root@centos7 tmp]# set +C3. 特殊重定向符号
在bash中 , <<和<<<是特殊重定向符号。<<表示的是here document , <<<表示的是here string。
3.1 here document
Here Document 是在Linux Shell 中的一种特殊的重定向方式 , 它的基本的形式如下
cmd << delimiter
Here Document Content
delimiter它的作用就是将两个 delimiter 之间的内容(Here Document Content部分) 传递给cmd 作为输入参数。
注意:
delimiter可以使用任意合法字符结尾的
delimiter一定要顶格写 , 前不能有任何字符结尾的
delimiter前后不能有任何字符及空格 , 要和开始的delimiter保持一致开头
delimiter前后的空格会被省略掉输出时加上双引号可以换行
当
delimiter加上单引号或者双引号时变量将不生效当
delimiter前加上-可无视tab键
比如在终端中输入cat << EOF , 系统会提示继续进行输入 , 输入多行信息再输入EOF , 中间输入的信息将会显示在屏幕上。如下:
[root@centos7 ~]# cat << EOF
> First Line
> Second Line
> Third Line EOF
> EOF
First Line
Second Line
Third Line EOF注:
>这个符号是终端产生的提示输入信息的标识符
一方面 , eof部分都必须使用<<eof , 它表示here document , 此后输入的内容都作为一个document输入给cat。既然是document , 那就肯定有document结束符标记document到此结束 , 结束符使用的是here document后的字符 , 例如此处为eof。其实不使用eof , 使用其他字符也是一样的 , 但document的结束符也必须要随之改变。如:
[root@centos7 ~]# cat <<abcx
> 123
> 345
> abcx
123
345另一方面 , >log1.txt表示将document的内容覆盖到log1.txt文件中 , 如果是要追加 , 则使用>>log1.txt。所以追加的方式如下:
[root@centos7 tmp]# cat >>log1.txt << eof
> this is stdin character first!
> eof或
[root@centos7 tmp]# cat << eof >>log1.txt
> this is stdin character first!
> eof实例
免交互行数的统计
[root@centos7 ~]# wc -l << EOF > 1 > 2 > EOF 2免交互读取read命令并打印
[root@centos7 ~]# read input <<EOF > hello > bor > EOF [root@centos7 ~]# echo $input hello免交互修改密码
[root@centos7 ~]# passwd xiong << EOF > 123456 > 123456 > EOF Changing password for user xiong. New password: BAD PASSWORD: The password is shorter than 8 characters Retype new password: passwd: all authentication tokens updated successfully.免交互变量替换
#将变量写入到某个文件中 [root@centos7 ~]# cat demo.sh #!/bin/bash catName=mimi cat > catHome <<EOF Cat name is $catName! EOF [root@centos7 ~]# bash demo.sh [root@centos7 ~]# cat catHome Cat name is mimi!变量整体免交互赋值
[root@centos7 ~]# cat demo.sh #!/bin/bash catName=mimi result=$(cat <<EOF time is "`date`" Cat name is $catName! EOF ) echo $result [root@centos7 ~]# bash demo.sh time is "Wed Aug 25 10:35:44 CST 2021" Cat name is mimi!多行注释
Here Document的引入解决多行注释问题 使用:代表空指令 , 中间标记区域的内容不会被执行[root@centos7 ~]# cat demo.sh #!/bin/bash catName=mimi : <<EOF time is "`date`" Cat name is $catName! EOF echo ">3" [root@centos7 ~]# bash demo.sh >3
3.2 here string
对于here string , 表示将<<<后的字符串作为输入数据。
例如:
passwd --stdin user <<< password_value等价于:
echo password_value | passwd --stdin user4. 重定向相关命令
4.1 tee
可以使用tee双重定向。一般情况下 , 重定向要么将信息输入到文件中 , 要么输出到屏幕上 , 但是既想输出到屏幕又想输出到文件就比较麻烦。使用tee的双重定向功能可以实现该想法。

语法 :
tee [-a] file1 file2 file3选项说明
-a: 默认是将输出覆盖到文件中 , 使用该选项将变为追加行为。file: 除了输出到标准输出中 , 还将输出到file中。如果file为-, 则表示再输入一次到标准输出中。
tee的作用是将一份标准输入多重定向 , 一份重定向到标准输出/dev/stdout , 然后还将标准输入重定向到每个文件FILE中。
$ cat alpha.log | tee file1 file2 file3 | cat
$ cat alpha.log | tee file1 file2 file3 >/dev/null上面第一个命令将alpha.log的文件内容重定向给file{1..3}和标准输出通过管道传递给cat; 上面第二个命令将alpha.log的文件内容重定向给file{1..3}和/dev/null。
示例
将
ls的数据存一份到~/homefile, 同时屏幕也有输出信息。$ ls -l /home | tee ~/homefile | more将
a开头的文件内容全部保存到b.log, 同时把副本交给后面的的cat, 使用这个cat又将内容保存到了x.log。其中-代表前面的stdin。[root@centos7 tmp]# cat a* | tee b.log | cat - >x.log直接输出到屏幕:
[root@centos7 tmp]# cat a* | tee b.log | cattee默认会使用覆盖的方式保存到文件 , 可以使用-a选项来追加到文件。如:[root@centos7 tmp]# cat a* | tee -a b.log | cat现在就可以在使用cat和重定向创建文件或写入内容到文件的同时又可以在屏幕上显示一份。
[root@centos7 tmp]# cat <<eof | tee ttt.txt > x y > z 1 > eof x y z 1
4.1.1 tee重定向给多个命令
写多了脚本的人可能遇到过这样一种需求 : 将一份标准输入 m 重定向到多个命令中去。大概是这样的:
| CMD1
↗
INPUT | tee
↘
| CMD2其实bash自身的特性就能实现这样的需求 , 通过重定向到子shell中 , 就能模拟一个文件重定向行为:
cat alpha.txt | tee >(grep -E "a|b") >(grep -E "d|b|c")实际上这里的两个>(cmd_list)不是重定向 , 而是进程替换。命令行解析开始时 , 将首先进行进程替换 , 这两个grep将等待标准输入。然后启动cat和tee , 然后tee将标准输出交给两个进程的标准输入。
上面的命令将alpha.txt文件内容重定向为3份 : 一份给第一个grep命令 , 一份给第二个grep命令 , 一份给标准输出。假如alpha.txt的内容是a b c d e5个字母分别占用5行(每行一个字母) , 上面的输出结果如下:
[root@centos7 ~]# cat test | tee >(grep -E "a|b") >(grep -E "d|b|c")
a
b
c
d
e # 前5行是重定向到/dev/stdout的
a
b # 这2行是重定向给第一个grep后的执行结果
b
c
d # 这3行是重定向给第二个grep后的执行结果如果不想要给标准输出的那份重定向 , 加上>/dev/null:
[root@centos7 ~]# cat test | tee >(grep -E "a|b") >(grep -E "d|b|c") > /dev/null
b
c
d
[root@centos7 ~]# a
b
4.1.2 tee重定向给多个命令时的问题
tee将数据重定向给不同命令时 , 这些命令是独立执行的 , 它们都会各自打开一个属于自己的STDOUT , 如果它们都重定向到标准输出 , 由于涉及到多个不同的/dev/stdout , 它们的结果将出现两个问题 :
不保证有序性
因为跨了命令 , 交互式模式下(默认标准输出为屏幕)可能会出现命令行隔断的问题(非交互式下不会有问题)
$ cat alpha.txt | tee >(grep -E "a|b") >(grep -E "d|b|c") >/dev/null
$ a # 结果直接出现在提示符所在行
b
b
c
d
$ cat alpha.txt | tee >(grep -E "a|b") >(grep -E "d|b|c") >/dev/null
b
c # 这次的结果和上次的顺序不一样
d
a
b这两个问题 , 在写脚本过程中必须解决。
对于第二个问题 : 不同/dev/stdout同时输出时在屏幕上交叉输出的问题 , 只需将它们再次重定向走即可 , 这样两份不同的/dev/stdout都再次同时作为一份标准输入
$ cat alpha.txt | tee >(grep -E "a|b") >(grep -E "d|b|c") >/dev/null | cat对于第一个问题 : 不同/dev/stdout同时输出时 , 输出顺序的随机性 , 这个没有好方法 , 只能在各命令行中将各自的结果保存到文件中
$ cat alpha.txt | tee >(grep -E "a|b" >file1) >(grep -E "d|b|c" >file2) >/dev/null所以 , tee在重定向到多个命令中是有缺陷的 , 或者说用起来非常不方便 , 只要将各命令的结果各自保存时, 才能一切按照自己的
4.2 pee
pee是moreutils包中的一个小工具 , 先安装它(epel源中有):
yum -y install moreutils语法 :
pee ["cmds"][root@centos7 ~]# cat test | pee 'grep -E "a|b"' 'grep -E "b|c|d"' a b b c d
和tee有同样的问题 , 如果各命令都没有指定自己的标准输出重定向 , 它们将各自打开一个属于自己的/dev/stdout , 同样会有多个/dev/stdout同时输出时结果数据顺序随机性的问题 , 但是不会有多个/dev/stdout同时输出时交互式的隔断性问题 , 因为pee会收集各个命令的标准输出 ,然后将收集的结果作为自己的标准输出。
pee和tee最大的不同 , 在于pee将来自多个不同命令的结果作为pee自己的标准输出 ,所以下面的命令是可以像普通命令一样进行重定向的。
INPUT | pee CMD1 CMD2 >/FILE而tee则不同 , 是将cmd1和cmd2的结果放进标准输出(假设各命令自身没有使用重定向) , 保存到FILE中的是tee读取的标准输入。
INPUT | tee >(cmd1) >(cmd2) >/FILE所以 , 想要重定向`tee中cmd1和cmd2的总结果 , 必须使用额外的管道 , 或者将整个tee放进子shell。
INPUT | tee >(cmd1) >(cmd2) >/dev/null | cat >FILE1
INPUT | ( tee >(cmd1) >(cmd2) >/dev/null ) >/FILE1参考链接
四、IO重定向和管道以及基本文本处理工具 - 幻落之瞳 - 博客园 (cnblogs.com)
Linux重定向 ( 输入输出重定向 ) 详解 (biancheng.net)
结合Linux文件描述符谈重定向 , 彻底理解重定向的本质!_跳墙网 (tqwba.com)
Linux Shell重定向 ( 输入输出重定向 ) 精讲 (biancheng.net)
SHELL脚本--管道和重定向基础 - 骏马金龙 - 博客园 (cnblogs.com)
linux shell 的here document 用法 (cat << EOF) - GreatFish的个人空间 - OSCHINA - 中文开源技术交流社区
Shell脚本 - Here Document免交互与Expect自动化交互 | 实例操作 | 超详细_serendipity_cat的博客-CSDN博客
Linux Shell教程 ( 二 ) / tee双重重定向命令 - 汇智网 (hubwiz.com)
Linux tee的花式用法和pee - 骏马金龙 - 博客园 (cnblogs.com)
Linux Shell教程 ( 二 ) / 管线命令介绍 - 汇智网 (hubwiz.com)