一、 写在前面
在 Bash 脚本和命令行操作中,输出重定向是一项基本且强大的功能。
它允许用户控制命令的输出流,将数据从一个地方转移到另一个地方,实现更加灵活和高效的工作流程。
本文旨在记录 Bash 中几种常见的输出重定向方法,包括:
> file
>file 2>&1 vs 2>&1 >file
| (pipe)
二、 基本概念
文件描述符(File Descriptor, FD)是 Unix 和 Unix-like 操作系统中的基本概念,用于表示一个进程打开的文件、管道、套接字或其他输入/输出资源。每个进程都有一个文件描述符表,管理其所有打开的文件。其中,对于系统管理员来说,STDIN、STDOUT 和 STDERR 这几个术语应该很熟悉,对应于程序内的三个文件描述符:0、1 和 2。
STDIN、STDOUT 和 STDERR 是三个标准的输入输出流,是操作系统为进程提供的默认输入和输出机制:
标准输入(STDIN):
描述:STDIN 是标准输入流,通常从键盘输入文件描述符:0默认来源:键盘作用:用于从用户或其他进程读取输入数据
标准输出(STDOUT):
描述:STDOUT 是标准输出流,通常显示在屏幕上文件描述符:1默认目标:终端(屏幕)作用:用于向用户或其他进程输出数据示例:echo "Hello, World!"在这个示例中,echo 命令将字符串输出到 STDOUT,默认情况下显示在屏幕上。
标准错误(STDERR):
描述:STDERR 是标准错误流,专门用于输出错误消息;文件描述符:2默认目标:终端(屏幕)作用:用于输出错误和诊断信息,不会与正常输出混淆;示例: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"。
执行脚本如下:
因此,在默认条件下,两者的关系如下:
三、 输出重定向
以下是几种常见的输出重定向方法及其用途:
> file
将标准输出(STDOUT)重定向到文件。如果文件不存在,则创建该文件,如果文件存在,则覆盖该文件的内容。
echo "Hello, World!" > output.txt
这个命令将字符串 "Hello, World!" 输出到 output.txt 文件中。
>file 2>&1
与2>&1 >file
"> file
" 和 "2>&1
" 是重定向的语法,用于分别控制标准输出(stdout)和标准错误输出(stderr)。
理解 ">file 2>&1
" 和 "2>&1 >file
" 需要理解命令重定向的顺序以及它们各自的作用。
">file 2>&1" 命令解释:"> file":将标准输出重定向到 file 文件;"2>&1":将标准错误输出重定向到标准输出(在这时,标准输出已经被重定向到 file);
因此,所有的输出(标准输出和标准错误输出)都会被重定向到 file 文件中。
"2>&1 >file" 命令解释:"2>&1":将标准错误输出重定向到标准输出(在这时,标准输出还指向终端);"> file":将标准输出重定向到 file 文件;
在这个顺序中,标准错误输出仍然重定向到原始的标准输出(终端),而标准输出被重定向到 file 文件。
结果是:
标准输出被重定向到 file 文件;标准错误输出仍然输出到终端;
假设命令 ls 629 会产生一个标准错误输出。
使用 > file 2>&1
在 output.txt 文件中,你会看到标准错误信息:
使用 2>&1 > file
在 output.txt 文件中将会是空的,因为标准输出重定向到文件,而标准错误输出仍然显示在终端:
>file 2>&1:将标准输出和标准错误都重定向到 file;2>&1 >file:将标准输出重定向到 file,标准错误输出仍然显示在终端;
顺序在这里是关键,因为重定向是从左到右执行的
,所以不同的顺序会导致不同的结果。
| (pipe)
管道(pipe)用于将一个命令的标准输出作为下一个命令的标准输入。
ls -l | grep "txt"
这个命令将 ls -l 的输出通过管道传递给 grep "txt" 命令,从而过滤出包含 "txt" 的行。
【eg1】
:
将输出重定向到文件,可以使用 ">
",后面跟上文件名。例如,"pwd > my_test" 执行 pwd 命令,并将 STDOUT 的内容重定向到文件 "my_test"。
如前所述,STDOUT 和 STDERR 分别对应数字 1 和 2。因此,使用 "2> my_test" 表示将 STDERR 的内容重定向到文件。
用 test.sh 脚本演示:
在上例中,STDOUT 被重定向到文件 "test_out",因此执行后,"Hello stderr" 仍会输出到 /dev/tty,而 "Hello stdout" 则会写入文件。
如果使用 "2>
",结果就会相反:
文件输出本身可以一起使用,例如:
除了单独输出到文件外,还可以引用其他文件描述符。例如,使用 "2>&1
" 可将 STDERR 重定向到 STDOUT。由于 "2>1
" 的意思是 "将 STDERR 重定向到文件 1",因此添加"&1" 是为了引用 STDOUT,而不是文件。
整个概念如下,但由于当前输出是 /dev/tty,因此在使用上可能没有明显的区别:
下面是实现 "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 相同的输出。
【eg2】
:
在了解 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 的输出写入文件;
整个过程如下,最后只有 STDOUT 的内容被写入文件:
另一个更简单方式是 "&> file",可以达到将 STDOUT 和 STDERR 写入文件达到相同效果:
【eg3】
:
在使用命令时,通常会将它们与管道 "|" 的概念结合起来。"|" 的基本思想是将当前命令的 "STDOUT" 重定向到下一条命令的 "STDIN",如下面的流程所示:
在下面的示例中,只有 STDOUT 被重定向到 grep 命令,而 STDERR 仍被输出到 /dev/tty:
如果还需要通过管道发送 STDERR 的内容,其概念与向文件写入内容类似,需要:
将 STDERR 重定向到 STDOUT;将下一条命令的 STDIN 连接到当前命令的 STDOUT;
五、 最后
本文简要介绍了 Bash 中常见的重定向技术,理解其原理就不需要再死记硬背如何将 STDERR 和 STDOUT 重定向到同一个文件,可以更严谨地思考如何实现各种要求。
原创 滑翔的纸飞机