在Bash中重定向stderr和stdout


676

我想将一个进程的stdout和stderr都重定向到一个文件。我该如何在Bash中做到这一点?


2
我想说这是一个非常有用的问题。许多人不知道该怎么做,因为他们不必经常这样做,而且这不是Bash记录得最好的行为。
罗伯特Wm Ruedisueli

2
有时(通常)查看输出并将其重定向到文件很有用。请参阅下面的Marko答案。(我在这里这么说是因为,只要足以解决问题,就可以很容易地看着第一个被接受的答案,但是其他答案通常可以提供有用的信息。)
jvriesem

Answers:


759

在这里看看。应该:

yourcommand &>filename

(将stdout和都重定向stderr到文件名)。


29
根据Bash Hackers Wiki弃用了此语法。是吗?
Salman von Abbas

20
根据wiki.bash-hackers.org/scripting/obsolete的说法,它似乎已过时,因为它不是POSIX的一部分,但是bash手册页并未提及在不久的将来将其从bash中删除。手册页的确指定了“&>”相对于“>&”的首选项,否则该首选项等效。
chepner

13
我猜我们不应该使用&>,因为它不在POSIX中,并且普通的shell(例如“破折号”)不支持它。
山姆·沃特金斯

27
一个额外的提示:如果在脚本中使用它,请确保它以#!/bin/bash而不是开头#!/bin/sh,因为in需要bash。
Tor Klingberg 2013年

8
或&>>附加而不是覆盖。
亚历山大·贡奇

447
do_something 2>&1 | tee -a some_file

这将STDERR重定向到标准输出和标准输出到some_file 打印到stdout。


16
在AIX(ksh)上,您的解决方案有效。接受的答案do_something &>filename不是。+1。
2013年

11
@Daniel,但是这个问题专门针对bash
John La Rooy

3
Ambiguous output redirect.知道为什么吗?
亚历山大·霍尔顿·戴利2014年

1
我有一个ruby脚本(我不想以任何方式修改),以红色粗体显示错误消息。然后从我的bash脚本(可以修改)中调用该ruby脚本。当我使用上面的代码时,它将以纯文本格式(减去格式)打印错误消息。是否有任何方法可以保留屏幕格式并在文件中获取输出(stdout和stderr都包括在内)?
亚特兰蒂斯2014年

8
请注意,(默认情况下)这具有以下副作用:$?不再引用的退出状态do_something,而是引用的退出状态tee
Flimm 2015年

254

您可以将stderr重定向到stdout,然后将stdout重定向到文件中:

some_command >file.log 2>&1 

参见http://tldp.org/LDP/abs/html/io-redirection.html

该格式比仅在bash中起作用的最受欢迎的&>格式更受青睐。在Bourne Shell中,它可以解释为在后台运行命令。同样,格式更易读2(是STDERR)重定向到1(STDOUT)。

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


47
这会将stderr重定向到原始stdout,而不重定向到stdout将要运行的文件。将'2>&1'放在'> file.log'之后,即可使用。

1
与some_command&> file.log相比,这种方法的优势是什么?
ubermonkey

6
如果要附加到文件,则必须采用以下方式:echo“ foo” 2>&1 1 >> bar.txt AFAIK无法使用&>附加内容
SlappyTheFish 2010年

9
抱歉,回声“ foo” 1 >> bar.txt 2>&1
SlappyTheFish 2010年

11
我认为2>&1将stderr重定向到stdout的解释是错误的;我认为将stderr发送到stdout此时正要到达的同一位置更为准确。因此在第一个重定向之后放置2>&1 是必不可少的。
2015年

200
# 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'"

现在,简单的回显将写入$ LOG_FILE。对守护有用。

对于原始帖子的作者,

这取决于您需要实现的目标。如果您只需要重定向从脚本中调用的命令,就可以给出答案。我的是关于当前脚本中进行重定向这会影响上述代码段之后的所有命令/内置命令(包括forks)。


另一个很酷的解决方案是立即重定向到std-err / out并同时重定向到记录器或日志文件,这涉及将“流”分成两部分。此功能由'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(FD#1)和/ dev / stderr(FD#2)。实际上,它可以是管道,插座或其他任何东西。

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

运行具有以上一行以及另外一行的脚本的结果:

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

...如下:

$ ./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

如果要查看更清晰的图片,请将以下两行添加到脚本中:

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

1
只有一个例外。在第一个示例中,您编写了:exec 1 <> $ LOG_FILE。这会导致原始日志文件始终被重写。对于真正的登录,更好的方法是:exec 1 >> $ LOG_FILE导致始终附加日志。
Znik

4
尽管这取决于意图,这是事实。我的方法是始终创建唯一且带有时间戳的日志文件。另一种是附加。两种方式都是“对数可旋转的”。我更喜欢需要较少解析的单独文件,但是正如我所说的,无论什么使您的船浮起:)
quizac 2014年

1
您的第二个解决方案是提供信息的,但是所有清除代码都包含什么?它似乎无关紧要,如果这样的话,只会混淆一个很好的例子。我还希望看到它稍作修改,以使FD 1和2不会重定向到记录器,而是3和4以便在通常的假设stdout == 1的情况下,调用此脚本的任何人都可以进一步操纵1和2。和stderr == 2,但是我的简短实验表明,这更加复杂。
JFlo

1
我更喜欢使用清理代码。可能会偏离核心示例,但是删除它会使示例不完整。网络上已经充满了没有错误处理的示例,或者至少有一个友好的提示,即仍然需要大约一百行代码才能安全使用。
Zoltan K.

1
我想详细说明清理代码。这是脚本的一部分,它可以使ergo变得不受挂起信号的影响。“ tee”和“ logger”是由相同的PPID生成的进程,它们从主bash脚本继承HUP陷阱。因此,一旦主进程死亡,它们就会被init [1]继承。他们不会成为僵尸(已解散)。如果主脚本死亡,清理代码可确保杀死所有后台任务。它也适用于可能已在后台创建并运行的任何其他进程。
quizac

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

1>file.log指示Shell将STDOUT发送到文件file.log,并2>&1告诉它将STDERR(文件描述符2)重定向到STDOUT(文件描述符1)。

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


21

奇怪的是,这有效:

yourcommand &> filename

但这给出了语法错误:

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

您必须使用:

yourcommand 1>> filename 2>&1

10
&>>似乎可以在BASH 4上使用:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
user272735 2011年

15

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


说明:

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

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

请注意,“&”运算符告诉bash 2是文件描述符(指向stderr),而不是文件名。如果我们省略了“&”,此命令将打印stdout到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本身!!!)(请参阅指针上的此资源有关更多信息))。由于文件描述符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 2>&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 或在我的示例中:

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

关键要点:

  • 文件描述符的行为类似于指针(尽管文件描述符与文件指针不同)
  • 将文件描述符“ a”重定向到指向文件“ f”的文件描述符“ b”,会导致文件描述符“ a”指向与文件描述符b相同的位置-文件“ f”。它不形成指针链a-> b-> f
  • 由于上述原因,顺序很重要,2>&1 >/dev/null就是!= >/dev/null 2>&1。一个产生输出,而另一个不产生!

最后看看这些出色的资源:

对重定向猛砸文档文件描述表的说明介绍指针


文件描述符(0、1、2)只是表的偏移量。当使用2>&1时,效果为插槽FD [2] = dup(1),因此FD [1]指向FD [2]的位置现在都指向该位置。当您将FD [1]更改为指向/ dev / null时,FD [1]将被更改,但它不会更改FD [2]插槽(指向标准输出)。我使用术语dup(),因为这是用于复制文件描述符的系统调用。
PatS'Mar

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" )

与之相关:将stdOut&stderr写入syslog。

它几乎可以工作,但不是从信德()


我猜因为“ / dev / fd / 3权限被拒绝”而无法使用。更改为>&3可能会有所帮助。
quizac

6

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

这是我发现的解决方案:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 第一次交换stderr和stdout
  • 然后将标准输出附加到日志文件
  • 将stderr管道连接到tee并将其附加到日志文件

顺便说一句,这对我不起作用(日志文件为空)。| tee无效。相反,我使用stackoverflow.com/questions/692000/…
Yaroslav Bulatov

4

对于这种情况,当需要“管道”时,可以使用:

|&

例如:

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的解决方案可以分别传递STDOUT和STDERR(从“ sort -c”的STDERR或从STDERR到“ sort -h”的管道)。


1

“最简单”的方式(bash4只)ls * 2>&- 1>&-


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"

当我使用您的函数并尝试还原标准输出时,我得到回显:写入错误:错误的文件编号重定向工作正常...还原似乎不起作用
thom schumacher

为了使您的脚本可用,我不得不注释掉以下几行,并更改了顺序:#exec 1>&-#关闭FD 1(日志文件)#exec 2>&-#关闭FD 2(日志文件);exec 1>&3 #restore stdout exec 2>&4 #restore stderr
schumacher

很抱歉听到这个消息。在CentOS 7,bash 4.2.46中运行时,我没有收到任何错误。我已经在获得这些命令的地方注释了参考。它是:Ref:logan.tw/posts/2016/02/20/open-and-close-files-b-bash
Fernando Fabreti

我在AIX上运行这些命令,这可能就是原因。我为我所做的修复添加了一个帖子。
thom schumacher

1

@ fernando-fabreti

除了您所做的之外,我还稍微更改了功能,并删除了&-结束符,它对我有用。

    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"

1

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

function myfunc(){
  [...]
}

myfunc &>mylog.log

0

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

command >& file

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.