关于shell:在Bash中重定向stderr和stdout

Redirect stderr and stdout in Bash

我想将进程的stdout和stderr都重定向到单个文件。在巴什我该怎么做?


看看这里。应该是:

1
yourcommand &>filename

(将stdoutstderr重定向到文件名)。


1
do_something 2>&1 | tee -a some_file

这将把stderr重定向到stdout,stdout重定向到some_file并将其打印到stdout。


您可以将stderr重定向到stdout,并将stdout重定向到文件:

1
some_command >file.log 2>&1

请参阅http://tldp.org/ldp/abs/html/io-redirection.html

此格式比仅在bash中工作的最流行的&;>格式更受欢迎。在bourne shell中,可以将其解释为在后台运行命令。此外,格式更可读2(是stderr)重定向到1(stdout)。

编辑:更改注释中指出的顺序


1
2
3
4
5
6
7
8
9
10
11
12
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-

# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE

# Redirect STDERR to STDOUT
exec 2>&1

echo"This line will appear in $LOG_FILE, not 'on screen'"

现在,SimpleEcho将写入$log_文件。用于守护进程。

致原帖作者:

这取决于你需要实现什么。如果您只需要重定向您从脚本调用的命令的输入/输出,那么已经给出了答案。我的是关于在当前脚本中重定向的问题,它会影响上述代码段之后的所有命令/内置程序(包括fork)。

另一个很酷的解决方案是同时重定向到std err/out和logger或log文件,这涉及到将"一个流"拆分为两个流。此功能由"tee"命令提供,该命令可以一次写入/附加到多个文件描述符(文件、套接字、管道等):tee file1 file2…>(命令1)>(命令2)…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT


get_pids_of_ppid() {
    local ppid="$1"

    RETVAL=''
    local pids=`ps x -o pid,ppid | awk"\\$2 == \"$ppid\" { print \\$1 }"`
    RETVAL="$pids"
}


# Needed to kill processes running in background
cleanup() {
    local current_pid element
    local pids=("$$" )

    running_pids=("${pids[@]}")

    while :; do
        current_pid="${running_pids[0]}"
        [ -z"$current_pid" ] && break

        running_pids=("${running_pids[@]:1}")
        get_pids_of_ppid $current_pid
        local new_pids="$RETVAL"
        [ -z"$new_pids" ] && continue

        for element in $new_pids; do
            running_pids+=("$element")
            pids=("$element""${pids[@]}")
        done
    done

    kill ${pids[@]} 2>/dev/null
}

所以,从一开始。假设我们的终端连接到/dev/stdout(fd_1)和/dev/stderr(fd_2)。实际上,它可以是管道、插座或其他什么东西。

  • 创建FDS 3和4,并分别指向与1和2相同的"位置"。从现在起更改fd 1不会影响fd 3。现在,fds 3和4分别指向stdout和stderr。这些将用作真正的终端stdout和stderr。
  • 1>>(…)将stdout重定向到parens中的命令
  • parens(子shell)执行从exec的stdout(pipe)读取的"tee",并通过parens中的另一个管道重定向到子shell,从而重定向到"logger"命令。同时,它将相同的输入复制到fd 3(终端)
  • 第二部分非常相似,是关于对stderr和fds 2和4执行相同的技巧。

运行具有上述行的脚本以及此脚本的结果:

1
echo"Will end up in STDOUT(terminal) and /var/log/messages"

…如下:

1
2
3
4
5
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages

$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages

如果希望看到更清晰的图片,请在脚本中添加这两行:

1
2
ls -l /proc/self/fd/
ps xf


1
bash your_script.sh 1>file.log 2>&1

1>file.log指示shell向文件file.log发送stdout,2>&1指示它将stderr(文件描述符2)重定向到stdout(文件描述符1)。

注:订单事项如liw.fi所指出,2>&1 1>file.log不起作用。


奇怪的是,这项工作:

1
yourcommand &> filename

但这会产生语法错误:

1
2
yourcommand &>> filename
syntax error near unexpected token `>'

你必须使用:

1
yourcommand 1>> filename 2>&1


简短回答:Command >filename 2>&1Command &>filename

说明:

考虑下面的代码,它将单词"stdout"打印到stdout,将单词"stderror"打印到stderror。

1
2
3
$ (echo"stdout"; echo"stderror">&2)
stdout
stderror

请注意,"&;"运算符告诉bash 2是文件描述符(指向stderr),而不是文件名。如果省略"&;",此命令将把stdout打印到stdout,并创建一个名为"2"的文件,并在其中写入stderror

通过试验上面的代码,您可以自己确切地看到重定向操作符是如何工作的。例如,通过更改将两个描述符中的哪一个重定向到1,2的文件,下面的两行代码分别删除stdout中的所有内容和stderror中的所有内容(打印剩余内容)。

1
2
3
4
$ (echo"stdout"; echo"stderror">&2) 1>/dev/null
stderror
$ (echo"stdout"; echo"stderror">&2) 2>/dev/null
stdout

现在,我们可以解释为什么以下代码不产生输出的解决方案:

1
(echo"stdout"; echo"stderror">&2) >/dev/null 2>&1

为了真正理解这一点,我强烈建议您在文件描述符表上阅读此网页。假设你已经读过了,我们可以继续。注意,bash从左向右处理;因此bash首先看到>/dev/null(与1>/dev/null相同),并将文件描述符1设置为指向/dev/null而不是stdout。这样做之后,bash向右移动,看到2>&1。这会将文件描述符2设置为指向与文件描述符1相同的文件(而不是指向文件描述符1本身!!!!!(有关详细信息,请参阅指针上的此资源)。由于文件描述符1指向/dev/null,而文件描述符2指向与文件描述符1相同的文件,因此文件描述符2现在也指向/dev/null。因此,两个文件描述符都指向/dev/null,这就是不呈现输出的原因。

要测试您是否真正了解这个概念,请尝试在切换重定向顺序时猜测输出:

1
(echo"stdout"; echo"stderror">&2)  2>&1 >/dev/null

斯特德罗

< /块引用>

这里的理由是,从左到右进行计算时,bash看到2>&;1,因此将文件描述符2设置为指向与文件描述符1相同的位置,即stdout。然后,它将文件描述符1(记住>/dev/null=1>/dev/null)设置为指向>/dev/null,从而删除通常发送到标准输出的所有内容。因此,我们只剩下那些没有发送到子shell中stdout的代码(括号中的代码),即"stderror"。值得注意的是,尽管1只是指向stdout的指针,但是通过2>&1将指针2重定向到1并不会形成指针链2->1->stdout。如果是这样,由于将1重定向到/dev/null,代码2>&1 >/dev/null将给出指针链2->1->/dev/null,因此与我们上面看到的相反,代码将不会生成任何内容。

最后,我注意到有一种更简单的方法可以做到:

从这里的第3.6.4节,我们看到可以使用操作符&>来重定向stdout和stderr。因此,要将任何命令的stderr和stdout输出重定向到\dev
ull
(这会删除输出),我们只需键入$ command &> /dev/null或者在我的例子中:

1
$ (echo"stdout"; echo"stderror">&2) &>/dev/null

关键外卖:

  • 文件描述符的行为类似于指针(尽管文件描述符与文件指针不同)
  • 将文件描述符"a"重定向到指向文件"f"的文件描述符"b",使文件描述符"a"指向与文件描述符b-文件"f"相同的位置。它不构成指针链a->b->f
  • 正因为如此,订单问题,2>&1 >/dev/null是!=>/dev/null 2>&1。一个生成输出,另一个不生成!

最后看看这些伟大的资源:

关于重定向的bash文档,文件描述符表的解释,指针的介绍


1
2
3
4
5
6
7
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"

exec 3>&1 > >(tee -a /dev/fd/3 | logger -p"$LOG_FACILITY" -t"$LOG_TOPIC_OUT" )
exec 2> >(logger -p"$LOG_FACILITY" -t"$LOG_TOPIC_ERR" )

它是相关的:将stdout&stderr写入系统日志。

它几乎可以工作,但不是从新特来的;(


我想要一个解决方案,将stdout加上stderr的输出写入日志文件,stderr仍然在控制台上。所以我需要通过tee复制stderr输出。

这就是我发现的解决方案:

1
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 首先交换stderr和stdout
  • 然后将stdout附加到日志文件中
  • 将stderr连接到tee并将其附加到日志文件


在需要"管道"的情况下,您可以使用:

|&

例如:

1
2
3
echo -ne"15
100
"
|sort -c |& tee >sort_result.txt

1
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log  ; done |& sort -h

这个基于bash的解决方案可以单独传输stdout和stderr(从"sort-c"的stderr或从stderr到"sort-h")。


以下函数可用于自动切换输出Beetwen stdout/stderr和日志文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/bin/bash

    #set -x

    # global vars
    OUTPUTS_REDIRECTED="false"
    LOGFILE=/dev/stdout

    #"private" function used by redirect_outputs_to_logfile()
    function save_standard_outputs {
        if ["$OUTPUTS_REDIRECTED" =="true" ]; then
            echo"[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
            exit 1;
        fi
        exec 3>&1
        exec 4>&2

        trap restore_standard_outputs EXIT
    }

    # Params: $1 => logfile to write to
    function redirect_outputs_to_logfile {
        if ["$OUTPUTS_REDIRECTED" =="true" ]; then
            echo"[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
            exit 1;
        fi
        LOGFILE=$1
        if [ -z"$LOGFILE" ]; then
            echo"[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"

        fi
        if [ ! -f $LOGFILE ]; then
            touch $LOGFILE
        fi
        if [ ! -f $LOGFILE ]; then
            echo"[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
            exit 1
        fi

        save_standard_outputs

        exec 1>>${LOGFILE%.log}.log
        exec 2>&1
        OUTPUTS_REDIRECTED="true"
    }

    #"private" function used by save_standard_outputs()
    function restore_standard_outputs {
        if ["$OUTPUTS_REDIRECTED" =="false" ]; then
            echo"[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
            exit 1;
        fi
        exec 1>&-   #closes FD 1 (logfile)
        exec 2>&-   #closes FD 2 (logfile)
        exec 2>&4   #restore stderr
        exec 1>&3   #restore stdout

        OUTPUTS_REDIRECTED="false"
    }

脚本内部使用示例:

1
2
3
4
5
echo"this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo"this goes to logfile"
restore_standard_outputs
echo"this goes to stdout"


"最简单"的方法(仅限于bash4):ls * 2>&- 1>&-


在考虑使用exec 2>&1之类的东西的情况下,如果可能的话,我会发现使用如下bash函数重写代码更容易阅读:

1
2
3
4
5
function myfunc(){
  [...]
}

myfunc &>mylog.log

@费尔南多·法布雷蒂

除了您所做的工作之外,我稍微更改了函数并删除了&;closing,这对我很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    function saveStandardOutputs {
      if ["$OUTPUTS_REDIRECTED" =="false" ]; then
        exec 3>&1
        exec 4>&2
        trap restoreStandardOutputs EXIT
      else
          echo"[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
          exit 1;
      fi
  }

  # Params: $1 => logfile to write to
  function redirectOutputsToLogfile {
      if ["$OUTPUTS_REDIRECTED" =="false" ]; then
        LOGFILE=$1
        if [ -z"$LOGFILE" ]; then
            echo"[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
        fi
        if [ ! -f $LOGFILE ]; then
            touch $LOGFILE
        fi
        if [ ! -f $LOGFILE ]; then
            echo"[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
            exit 1
        fi
        saveStandardOutputs
        exec 1>>${LOGFILE}
        exec 2>&1
        OUTPUTS_REDIRECTED="true"
      else
        echo"[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
          exit 1;
      fi
  }
  function restoreStandardOutputs {
      if ["$OUTPUTS_REDIRECTED" =="true" ]; then
      exec 1>&3   #restore stdout
      exec 2>&4   #restore stderr
      OUTPUTS_REDIRECTED="false"
     fi
  }
  LOGFILE_NAME="tmp/one.log"
  OUTPUTS_REDIRECTED="false"

  echo"this goes to stdout"
  redirectOutputsToLogfile $LOGFILE_NAME
  echo"this goes to logfile"
  echo"${LOGFILE_NAME}"
  restoreStandardOutputs
  echo"After restore this goes to stdout"

对于tcsh,我必须使用以下命令:

1
command >& file

如果使用command &> file,会出现"无效的空命令"错误。