763

我想将进程的标准输出标准错误都重定向到单个文件。我如何在 Bash 中做到这一点?

4

15 回答 15

850

看看这里。它应该是:

yourcommand &> filename

它将标准输出和标准错误都重定向到文件filename

于 2009-03-12T09:17:25.790 回答
500
do_something 2>&1 | tee -a some_file

这会将标准错误重定向到标准输出和标准输出some_file 并将其打印到标准输出。

于 2009-03-12T09:16:20.490 回答
285

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

some_command >file.log 2>&1

请参阅第 20 章。 I/O 重定向

&>这种格式比仅适用于 Bash的最流行的格式更受欢迎。在 Bourne shell 中,它可以被解释为在后台运行命令。格式也更具可读性 - 2(是标准错误)重定向到1(标准输出)。

于 2009-03-12T09:17:20.253 回答
232
# Close standard output file descriptor
exec 1<&-
# Close standard error file descriptor
exec 2<&-

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

# Redirect standard error to standard output
exec 2>&1

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

现在,一个简单的回显将写入 $LOG_FILE,它对于守护进程很有用。

致原帖作者,

这取决于你需要实现什么。如果您只需要重定向输入/输出从脚本调用的命令,那么答案已经给出。我的是关于在当前脚本中重定向,这会影响提到的代码片段之后的所有命令/内置插件(包括叉子)。


另一个很酷的解决方案是重定向到标准错误和标准输出,并立即记录到日志文件,这涉及将“一个流”分成两个。此功能由 'tee' 命令提供,该命令可以一次写入/附加到多个文件描述符(文件、套接字、管道等):tee FILE1 FILE2 ... >(cmd1) >(cmd2) ...

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(文件描述符 #1)和 /dev/stderr(文件描述符 #2)。在实践中,它可以是管道、套接字或其他任何东西。

  • 创建文件描述符(FD) #3 和 #4 并分别指向与 #1 和 #2 相同的“位置”。从现在开始,更改文件描述符 #1 不会影响文件描述符 #3。现在,文件描述符#3 和#4 分别指向标准输出和标准错误。这些将用作真正的终端标准输出和标准错误。
  • 1> >(...) 将标准输出重定向到括号中的命令
  • 括号(子shell)执行'tee',从exec的标准输出(管道)读取并通过另一个管道重定向到'logger'命令到括号中的子shell。同时它将相同的输入复制到文件描述符#3(终端)
  • 第二部分非常相似,是关于对标准错误和文件描述符#2 和#4 执行相同的技巧。

运行具有上述行和另外这一行的脚本的结果:

echo "Will end up in standard output (terminal) and /var/log/messages"

...如下:

$ ./my_script
Will end up in standard output (terminal) and /var/log/messages

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

如果您想看到更清晰的图片,请将这两行添加到脚本中:

ls -l /proc/self/fd/
ps xf
于 2013-12-13T10:30:31.397 回答
46
bash your_script.sh 1>file.log 2>&1

1>file.log指示 shell 将标准输出发送到文件file.log,并2>&1告诉它将标准错误(文件描述符 2)重定向到标准输出(文件描述符 1)。

注意:订单很重要,正如 liw.fi 指出的那样,2>&1 1>file.log不起作用。

于 2009-03-12T09:17:03.623 回答
22

奇怪的是,这有效:

yourcommand &> filename

但这会产生语法错误:

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

你必须使用:

yourcommand 1>> filename 2>&1
于 2009-05-06T14:12:30.160 回答
19

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


解释:

考虑以下代码,它将单词“stdout”打印到stdout,将单词“stderror”打印到stderror。

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

请注意,'&' 运算符告诉 bash 2 是文件描述符(指向 stderr)而不是文件名。如果我们省略了 '&',此命令将打印stdout到标准输出,并创建一个名为“2”的文件并stderror在那里写入。

通过试验上面的代码,您可以自己了解重定向运算符的工作原理。例如,通过更改两个描述符中的哪个文件1,2被重定向到/dev/null以下两行代码,分别从 stdout 和 stderror 中删除所有内容(打印剩余的内容)。

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

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

(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 本身!!!!(请参阅this resource on pointers)了解更多信息))。由于文件描述符 1 指向 /dev/null,并且文件描述符 2 指向与文件描述符 1 相同的文件,因此文件描述符 2 现在也指向 /dev/null。因此,两个文件描述符都指向 /dev/null,这就是不呈现输出的原因。


为了测试你是否真的理解这个概念,试着猜测我们切换重定向顺序时的输出:

(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 via2>&1不会形成指针链 2 -> 1 -> stdout。如果是这样,由于将 1 重定向到 /dev/null,代码2>&1 >/dev/null将给出指针链 2 -> 1 -> /dev/null,因此代码不会生成任何内容,与我们在上面看到的相反。


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

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

$ (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 文档文件描述符表的解释,指针简介

于 2017-11-19T01:55:27.873 回答
11
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" )

相关:将标准输出和标准错误写入syslog

它几乎可以工作,但不是来自xinetd ;(

于 2009-04-23T13:14:35.040 回答
9

对于需要“管道”的情况,您可以使用|&.

例如:

echo -ne "15\n100\n" | sort -c |& tee >sort_result.txt

或者

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

这些基于 Bash 的解决方案可以分别管道标准输出和标准错误(从“sort -c”的标准错误,或从“sort -h”的标准错误)。

于 2018-10-24T17:45:01.760 回答
7

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

这是我找到的解决方案:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 首先交换stderr和stdout
  • 然后将标准输出附加到日志文件
  • 管道 stderr 到 tee 并将其也附加到日志文件
于 2017-01-19T19:21:18.827 回答
2

除了费尔南多·法布雷蒂所做的之外,我稍微改变了功能并删除了&-关闭,它对我有用。

    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
  }

  # Parameters: $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 standard output"
  redirectOutputsToLogfile $LOGFILE_NAME
  echo "this goes to logfile"
  echo "${LOGFILE_NAME}"
  restoreStandardOutputs
  echo "After restore this goes to standard output"
于 2018-09-14T16:35:13.983 回答
2

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

function myfunc(){
  [...]
}

myfunc &>mylog.log
于 2019-04-22T19:16:16.773 回答
1

“最简单”的方式(仅限 Bash 4):

ls * 2>&- 1>&-
于 2016-02-04T13:57:37.810 回答
1

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

#!/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"
    }

脚本内部使用示例:

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"
于 2018-07-26T15:53:15.073 回答
0

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

command >& file

如果使用command &> file,它将给出“无效的空命令”错误。

于 2013-04-23T05:07:32.917 回答