Linux开发讲课18--- “>file 2>&1“ 和 “2>&1 >file“ 的区别

        在 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 重定向到同一个文件,可以更严谨地思考如何实现各种要求。

  • 37
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值