要彻底理解重定向,我们必须先来了解这些基础知识:
文件描述符
文件描述符(FD, File descriptor)是一个表述 打开的文件 的抽象化概念。
FD是一个非负整数,本质是内核文件表(每个task都有,由内核维护)的索引值。
当程序请求打开文件时,内核执行相关操作,然后向程序返回一个FD,指代此文件。
stdin、stdout、stderr
每个进程(除了守护进程)都会默认打开这三个文件:
stdin
:标准输入文件,对应键盘,文件描述符 FD 为 0;stdout
:标准输出文件,对应显示器,文件描述符 FD 为 1;stderr
:标准错误文件,对应显示器,文件描述符 FD 为 2;
我们可以使用lsof -p $$
命令查看当前 shell 打开的文件描述符信息;
从命令输出中可以发现 fd0、fd1、fd2 都是以 rw 读写模式打开的。
了解这些知识之后,我们再来学习一下 shell 重定向操作:
command 0<data.file
:重定向标准输入,0 表示 fd0(stdin),<
表示只读方式,即不再从键盘读取数据,而是从 data.file 读取数据;command 1>data.file
:重定向标准输出,1 表示 fd1(stdout),>
表示只写方式(会清空原文件),即不再往显示器写入数据,而是往 data.file 写入数据;command 1>>data.file
;重定向标准输出,1 表示 fd1(stdout),>>
表示追加方式(不清空原文件),即不再往显示器写入数据,而是往 data.file 写入数据;command 2>data.file
:重定向标准错误,2 表示 fd2(stderr),>
表示只写方式(会清空原文件),即不再往显示器写入数据,而是往 data.file 写入数据;command 2>>data.file
;重定向标准错误,2 表示 fd2(stderr),>>
表示追加方式(不清空原文件),即不再往显示器写入数据,而是往 data.file 写入数据;
其实可以和 C 语言中的 fopen() 中的 mode 联系起来,
<
即rb
、>
即wb
、>>
即ab
。
<
默认与fd0
关联,即<
和0<
一样>
或>>
默认与fd1
关联,即>
、>>
和1>
、1>>
一样
将 stdout、stderr 重定向到不同文件:
command >out.log 2>err.log
(写入);command >>out.log 2>>err.log
(追加)。
将 stdout、stderr 重定向至同一文件:
command >log 2>&1
(写入);command >>log 2>&1
(追加);command 2>log 1>&2
(写入);command 2>>log 1>&2
(追加);command &>log
(写入);command &>>log
(追加)。
三种方式产生的效果都是一样的(但里面有些细节不一样),其中第三种是简写方式,在某些时候有局限性。
如:我要将 stdout 和 stderr 合并,用管道传递给下一个命令,就不能使用方式三,只能为cmd1 2>&1 | cmd2
。
合并 stdout、stderr 流到其中一个流:
command 2>&1
:将 stderr 合并到 stdoutcommand 1>&2
:将 stdout 合并到 stderr
heredoc
有时候我不想从 stdin 读取数据,而是想现写,这时候就可以使用<<
,打开 heredoc 功能。
具体用法:command <<EOF
,其中 EOF
只是一个表示结束的字符串,你可以换成任意字符串;
当你按下回车后,你就可以写入任意数据了,写完后,新起一行,输入EOF
,按下回车就可以了。
command <<EOF
:不带引号的 EOF,允许 变量引用、命令替换;command <<'EOF'
:单引号的 EOF,关闭 变量引用、命令替换;command <<"EOF"
:双引号的 EOF,关闭 变量引用、命令替换。
无论带不带引号,heredoc 都使用单独的 EOF 行表示结束(前后不能有任何内容,且不需要引号)。
这个功能在 shell 脚本中很常用。比如,我想在脚本中输出一大段内容到一个文件中,heredoc 就派上用场了:
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# 检查参数
if [ $# -lt 2 ]; then
echo "Usage: $0 <listen_ip> <listen_port>" 1>&2
exit 1
fi
# 可以在 heredoc 中引用变量,非常方便
cat <<EOF >my.conf
listen_ip=$1
listen_port=$2
EOF
1
2
3
4
5
6
7
8
9
10
$ chmod +x test.sh
$ ./test.sh
Usage: ./test.sh <listen_ip> <listen_port>
$ ./test.sh 0.0.0.0 1080
$ cat my.conf
listen_ip=0.0.0.0
listen_port=1080
heredoc 变种
除了 <<EOF
外,我们还可以直接使用 <<<'string data'
或 <<<"string data"
或 <<<$'string data'
(如果没有空白符也可以不加引号,第 3 种会进行转义),它们的作用是一样的(重定向 stdin),<<EOF
适用于多行文本 ,<<<string
适用于单行文本串(当然也可以多行),例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat <<EOF
heredoc> www.baidu.com
heredoc> EOF
www.baidu.com
$ cat <<<www.baidu.com
www.baidu.com
$ cat <<<'www.baidu.com'
www.baidu.com
$ cat <<<"www.baidu.com"
www.baidu.com
$ cat <<<$'www.zfl9.com\nwww.baidu.com\nwww.google.com'
www.zfl9.com
www.baidu.com
www.google.com
重定向汇总
命令 | 说明 |
---|---|
CMD FD<FILENAME | 以rb 方式打开 FILENAME,将 FD 指向的文件改为 FILENAME,FD 默认为 0 |
CMD FD>FILENAME | 以wb 方式打开 FILENAME,将 FD 指向的文件改为 FILENAME,FD 默认为 1 |
CMD FD>>FILENAME | 以ab 方式打开 FILENAME,将 FD 指向的文件改为 FILENAME,FD 默认为 1 |
CMD FD1<&FD2 | 将 FD2 合并至 FD1(读取),FD1 默认为 0 |
CMD FD1>&FD2 | 将 FD1 合并至 FD2(写入),FD1 默认为 1 |
exec FD<FILENAME | 以rb 方式打开 FILENAME,并分配指定 FD(限制为 0-9),默认 FD 为 0 |
exec FD>FILENAME | 以wb 方式打开 FILENAME,并分配指定 FD(限制为 0-9),默认 FD 为 1 |
exec FD>>FILENAME | 以ab 方式打开 FILENAME,并分配指定 FD(限制为 0-9),默认 FD 为 1 |
exec FD<>FILENAME | 以rb+ 方式打开 FILENAME,并分配指定 FD(限制为 0-9),默认 FD 为 0 |
exec FD<&- | 关闭 FD 的输入,实际上 FD 会被释放,默认 FD 为 0 |
exec FD>&- | 关闭 FD 的输出,实际上 FD 会被释放,默认 FD 为 1 |
关于exec
打开自定义描述符的一些说明:
- 在 bash-4.4.12 中测试发现,FD 没有限制,可以大于 9;
- 在 zsh-5.4.2 中测试发现,FD 被限制为 0-9,不能是其它值。