在 Bash 脚本和命令行操作中,输出重定向是一项基本且强大的功能。它允许用户控制命令的输出流,将数据从一个地方转移到另一个地方,实现更加灵活和高效的工作流程。本文旨在记录 Bash 中几种常见的输出重定向方法,包括:
-. > file
-. >file 2>&1 vs 2>&1 >file
-. | (pipe)
2. 基本概念
文件描述符(File Descriptor, FD)是 Unix 和 Unix-like 操作系统中的基本概念,用于表示一个进程打开的文件、管道、套接字或其他输入/输出资源。每个进程都有一个文件描述符表,管理其所有打开的文件。其中,对于系统管理员来说,STDIN、STDOUT 和 STDERR 这几个术语应该很熟悉,对应于程序内的三个文件描述符:0、1 和 2。
STDIN、STDOUT 和 STDERR 是三个标准的输入输出流,是操作系统为进程提供的默认输入和输出机制:
标准输入(STDIN):
-
描述:STDIN 是标准输入流,通常从键盘输入
-
文件描述符:0
-
默认来源:键盘
-
作用:用于从用户或其他进程读取输入数据
-
示例:
root@jpzhang-dev:~# read name jpzhang root@jpzhang-dev:~# echo "Hello, $name" Hello, jpzhang
在这个示例中,read name 从 STDIN 读取用户输入。
标准输出(STDOUT):
-
描述:STDOUT 是标准输出流,通常显示在屏幕上
-
文件描述符:1
-
默认目标:终端(屏幕)
-
作用:用于向用户或其他进程输出数据
-
示例:
ls non_existent_file
在这个示例中,ls 命令会尝试列出一个不存在的文件,并将错误消息输出到 STDERR。
-
实际上,在程序中执行文件操作时,会生成新的文件描述符,通常从 3、4、5 开始,依次类推。内置的 0、1、2 最初并不指向任何文件,而是指向 /dev/tty。这意味着应用程序可以通过 STDIN 从 tty 向程序发送数据,反之亦然,它可以通过 STDOUT 和 STDERR 向 /dev/tty 输出不同类型的输出。
以下面脚本为例:test.sh
#!/bin/bash echo "Hello stdout" echo "Hello stderr" 1>&2
向 STDOUT 输出 "Hello stdout",向 STDERR 输出 "Hello stderr"。
执行脚本如下:
root@jpzhang-dev:/home/workspace/linux/std# bash test.sh Hello stdout Hello stderr
因此,在默认条件下,两者的关系如下:
3. 输出重定向
以下是几种常见的输出重定向方法及其用途:
3.1 > file
将标准输出(STDOUT)重定向到文件。如果文件不存在,则创建该文件,如果文件存在,则覆盖该文件的内容。
echo "Hello, World!" > output.txt
这个命令将字符串 "Hello, World!" 输出到 output.txt 文件中。
3.2 >file 2>&1 与 2>&1 >file
"> file" 和 "2>&1" 是重定向的语法,用于分别控制标准输出(stdout)和标准错误输出(stderr)。理解 ">file 2>&1" 和 "2>&1 >file" 需要理解命令重定向的顺序以及它们各自的作用。
3.2.1 >file 2>&1
">file 2>&1" 命令解释:
-
"> file":将标准输出重定向到 file 文件;
-
"2>&1":将标准错误输出重定向到标准输出(在这时,标准输出已经被重定向到 file);
因此,所有的输出(标准输出和标准错误输出)都会被重定向到 file 文件中。
3.2.2 2>&1 >file
"2>&1 >file" 命令解释:
-
"2>&1":将标准错误输出重定向到标准输出(在这时,标准输出还指向终端);
-
"> file":将标准输出重定向到 file 文件;
在这个顺序中,标准错误输出仍然重定向到原始的标准输出(终端),而标准输出被重定向到 file 文件。结果是:
-
标准输出被重定向到 file 文件;
-
标准错误输出仍然输出到终端;
3.2.3 具体示例
假设命令 ls non_existent_file
会产生一个标准错误输出。
-
使用
> file 2>&1
ls non_existent_file > output.txt 2>&1
在 output.txt 文件中,你会看到标准错误信息:
root@jpzhang-dev:/home/linux/std# cat output.txt
ls: 无法访问 'non_existent_file': 没有那个文件或目录
-
使用 2>&1 > file
ls non_existent_file 2>&1 > output.txt
在 output.txt 文件中将会是空的,因为标准输出重定向到文件,而标准错误输出仍然显示在终端:
root@jpzhang-dev:/home/linux/std# ls non_existent_file 2>&1 > output.txt
ls: 无法访问 'non_existent_file': 没有那个文件或目录
root@jpzhang-dev:/home/linux/std# cat output.txt
3.2.4 小结
-
>file 2>&1
:将标准输出和标准错误都重定向到 file; -
2>&1 >file
:将标准输出重定向到 file,标准错误输出仍然显示在终端;
顺序在这里是关键,因为重定向是从左到右执行的,所以不同的顺序会导致不同的结果。
3.3 | (pipe)
管道(pipe)用于将一个命令的标准输出作为下一个命令的标准输入。
ls -l | grep "txt"
这个命令将 ls -l 的输出通过管道传递给 grep "txt" 命令,从而过滤出包含 "txt" 的行。
4. 示例分析
4.1 示例 1
将输出重定向到文件,可以使用 ">",后面跟上文件名。例如,"pwd > my_test" 执行 pwd 命令,并将 STDOUT 的内容重定向到文件 "my_test"。
如前所述,STDOUT 和 STDERR 分别对应数字 1 和 2。因此,使用 "2> my_test" 表示将 STDERR 的内容重定向到文件。
用 test.sh 脚本演示:
root@jpzhang-dev:/home/linux/std# bash test.sh > test_out
Hello stderr
root@jpzhang-dev:/home/linux/std# cat test_out
Hello stdout
在上例中,STDOUT 被重定向到文件 "test_out",因此执行后,"Hello stderr" 仍会输出到 /dev/tty,而 "Hello stdout" 则会写入文件。
如果使用 "2>",结果就会相反:
root@jpzhang-dev:/home/linux/std# bash test.sh 2> test_out2
Hello stdout
root@jpzhang-dev:/home/linux/std# cat test_out2
Hello stderr
文件输出本身可以一起使用,例如:
root@jpzhang-dev:/home/linux/std# bash test.sh > test_out 2> test_out2
root@jpzhang-dev:/home/linux/std# cat test_out
Hello stdout
root@jpzhang-dev:/home/linux/std# cat test_out2
Hello stderr
除了单独输出到文件外,还可以引用其他文件描述符。例如,使用 "2>&1" 可将 STDERR 重定向到 STDOUT。由于 "2>1" 的意思是 "将 STDERR 重定向到文件 1",因此添加"&1" 是为了引用 STDOUT,而不是文件。
整个概念如下,但由于当前输出是 /dev/tty,因此在使用上可能没有明显的区别:
root@jpzhang-dev:/home/linux/std# bash test.sh 1>&2
Hello stdout
Hello stderr
下面是实现 "2>&1",将 STDERR 重定向到 STDOUT 的 C 代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
fwrite("For stdout\n", 11, 1, stdout);
fwrite("For stderr\n", 11, 1, stderr);
dup2(STDOUT_FILENO, STDERR_FILENO);
fwrite("To Stdout \n", 11, 1, stdout);
fwrite("To Stderr \n", 11, 1, stderr);
return 0;
}
执行 “dup2(STDOUT_FILENO, STDERR_FILENO);” 之后,所有输出到 stderr 的内容都将重定向到与 stdout 相同的输出。
4.2 示例 2
在了解 STDOUT 和 STDERR 之后,一个常见的要求是将 STDOUT 和 STDERR 写入同一个文件,其逻辑如下:
-
合并 stdout 和 stderr 的输出;
-
将合并结果输出到文件中;
因此,常见的解决方案是 "> file 2>&1", 错误用法是 "2>&1 > file" 两者虽看起来非常相似,但内在逻辑却不同;
首先,对于 "> file 2>&1",逻辑可以分为两个部分:
-
> file
=> 将 STDOUT 的输出写入文件; -
2>&1
=> 将 STDERR 的输出重定向到 STDOUT 的输入;
因此,STDOUT 和 STDERR 都可以写入文件。
对于 "2>&1 > file",如果把逻辑分解一下:
-
2>&1
=> 将 STDERR 的输出重定向到 STDOUT 的输出; -
> file
=> 将 STDOUT 的输出写入文件;
另一个更简单方式是 "&> file",可以达到将 STDOUT 和 STDERR 写入文件达到相同效果:
root@jpzhang-dev:/home/linux/std# bash test.sh &> qq
root@jpzhang-dev:/home/linux/std# cat qq
Hello stdout
Hello stderr
4.3 示例 3
在使用命令时,通常会将它们与管道 "|" 的概念结合起来。"|" 的基本思想是将当前命令的 "STDOUT" 重定向到下一条命令的 "STDIN",如下面的流程所示:
在下面的示例中,只有 STDOUT 被重定向到 grep 命令,而 STDERR 仍被输出到 /dev/tty:
root@jpzhang-dev:/home/linux/std# bash test.sh | grep test_out
Hello stderr
如果还需要通过管道发送 STDERR 的内容,其概念与向文件写入内容类似,需要:
-
将 STDERR 重定向到 STDOUT;
-
将下一条命令的 STDIN 连接到当前命令的 STDOUT;
root@jpzhang-dev:/home/linux/std# bash test.sh 2>&1 | grep xxx
root@jpzhang-dev:/home/linux/std# bash test.sh 2>&1 | grep err
Hello stderr
本文简要介绍了 Bash 中常见的重定向技术,理解其原理就不需要再死记硬背如何将 STDERR 和 STDOUT 重定向到同一个文件,可以更严谨地思考如何实现各种要求。