首页 Shell 重定向
文章
取消

Shell 重定向

要彻底理解重定向,我们必须先来了解这些基础知识:

文件描述符

文件描述符(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 合并到 stdout
  • command 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<FILENAMErb方式打开 FILENAME,将 FD 指向的文件改为 FILENAME,FD 默认为 0
CMD FD>FILENAMEwb方式打开 FILENAME,将 FD 指向的文件改为 FILENAME,FD 默认为 1
CMD FD>>FILENAMEab方式打开 FILENAME,将 FD 指向的文件改为 FILENAME,FD 默认为 1
CMD FD1<&FD2将 FD2 合并至 FD1(读取),FD1 默认为 0
CMD FD1>&FD2将 FD1 合并至 FD2(写入),FD1 默认为 1
exec FD<FILENAMErb方式打开 FILENAME,并分配指定 FD(限制为 0-9),默认 FD 为 0
exec FD>FILENAMEwb方式打开 FILENAME,并分配指定 FD(限制为 0-9),默认 FD 为 1
exec FD>>FILENAMEab方式打开 FILENAME,并分配指定 FD(限制为 0-9),默认 FD 为 1
exec FD<>FILENAMErb+方式打开 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,不能是其它值。
本文由作者按照 CC BY 4.0 进行授权

Shell 函数

Shell 管道