在Bash中重定向stderr和stdout

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

846804 次浏览
do_something 2>&1 | tee -a some_file

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

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

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

备注:顺序很重要,liw.fi指出,2>&1 1>file.log不起作用。

您可以将stderr重定向到标准输出并将标准输出重定向到文件中:

some_command >file.log 2>&1

第20章I/O重定向

此格式优于仅在Bash中工作的最流行的&>格式。在Bourne shell中,它可以被解释为在后台运行命令。此外,格式更具可读性-2(是标准错误)重定向到1(标准输出)。

看一下这里。它应该是:

yourcommand &> filename

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

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;(

奇怪的是,这是有效的:

yourcommand &> filename

但这给出了一个语法错误:

yourcommand &>> filenamesyntax error near unexpected token `>'

您必须使用:

yourcommand 1>> filename 2>&1

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

command >& file

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

# Close standard output file descriptorexec 1<&-# Close standard error file descriptorexec 2<&-
# Open standard output as $LOG_FILE file for read and write.exec 1<>$LOG_FILE
# Redirect standard error to standard outputexec 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 backgroundcleanup() {local current_pid elementlocal pids=( "$$" )
running_pids=("${pids[@]}")
while :; docurrent_pid="${running_pids[0]}"[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")get_pids_of_ppid $current_pidlocal new_pids="$RETVAL"[ -z "$new_pids" ] && continue
for element in $new_pids; dorunning_pids+=("$element")pids=("$element" "${pids[@]}")donedone
kill ${pids[@]} 2>/dev/null}

所以,从一开始,假设我们有一个终端连接到/dev/stdout(文件描述符#1)和 /dev/stderr(文件描述符#2),实际上,它可以是一个管道,套接字或其他什么。

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

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

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

…如下:

$ ./my_scriptWill end up in standard output (terminal) and /var/log/messages
$ tail -n1 /var/log/messagesSep 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

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

ls * 2>&- 1>&-

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

这是我找到的解决方案:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • 首先交换stderr和stdout
  • 然后将标准输出附加到日志文件中
  • 将stderr管道传输到T并将其附加到日志文件中

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


说明:

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

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

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

通过实验上面的代码,你可以亲眼看到重定向操作符是如何工作的。例如,通过更改两个描述符1,2中的哪个文件被重定向到/dev/null,以下两行代码分别删除标准输出中的所有内容和标准错误中的所有内容(打印剩余的内容)。

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

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

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

为了真正理解这一点,我强烈建议你阅读本文文件描述符表网页。假设你已经完成了阅读,我们可以继续进行。请注意,Bash从左到右处理;因此Bash首先看到>/dev/null(与1>/dev/null相同),并将文件描述符1设置为指向 /dev/null而不是标准输出。完成此操作后,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

stderror

这里的推理是,从左到右评估,Bash看到2>&1,因此将文件描述符2设置为指向与文件描述符1相同的位置,即stdout。然后将文件描述符1(记住>/dev/null=1>/dev/null)设置为指向>/dev/null,从而删除了通常会发送到标准out的所有内容。因此,我们剩下的就是在子shell中没有发送到stdout的内容(括号中的代码)-即“stderror”。有趣的是,即使1只是指向标准输出的指针,通过2>&1将指针2重定向到1并不会形成指针链2->1->标准输出。如果是这样,作为将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。一个生成输出,另一个不生成!

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

重定向的Bash文档文件描述符表的解释指针功能介绍

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

#!/bin/bash
#set -x
# global varsOUTPUTS_REDIRECTED="false"LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()function save_standard_outputs {if [ "$OUTPUTS_REDIRECTED" == "true" ]; thenecho "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"exit 1;fiexec 3>&1exec 4>&2
trap restore_standard_outputs EXIT}
# Params: $1 => logfile to write tofunction redirect_outputs_to_logfile {if [ "$OUTPUTS_REDIRECTED" == "true" ]; thenecho "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"exit 1;fiLOGFILE=$1if [ -z "$LOGFILE" ]; thenecho "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fiif [ ! -f $LOGFILE ]; thentouch $LOGFILEfiif [ ! -f $LOGFILE ]; thenecho "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"exit 1fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.logexec 2>&1OUTPUTS_REDIRECTED="true"}
# "private" function used by save_standard_outputs()function restore_standard_outputs {if [ "$OUTPUTS_REDIRECTED" == "false" ]; thenecho "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"exit 1;fiexec 1>&-   #closes FD 1 (logfile)exec 2>&-   #closes FD 2 (logfile)exec 2>&4   #restore stderrexec 1>&3   #restore stdout
OUTPUTS_REDIRECTED="false"}

脚本内部用法示例:

echo "this goes to stdout"redirect_outputs_to_logfile /tmp/one.logecho "this goes to logfile"restore_standard_outputsecho "this goes to stdout"

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

    function saveStandardOutputs {if [ "$OUTPUTS_REDIRECTED" == "false" ]; thenexec 3>&1exec 4>&2trap restoreStandardOutputs EXITelseecho "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"exit 1;fi}
# Parameters: $1 => logfile to write tofunction redirectOutputsToLogfile {if [ "$OUTPUTS_REDIRECTED" == "false" ]; thenLOGFILE=$1if [ -z "$LOGFILE" ]; thenecho "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"fiif [ ! -f $LOGFILE ]; thentouch $LOGFILEfiif [ ! -f $LOGFILE ]; thenecho "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"exit 1fisaveStandardOutputsexec 1>>${LOGFILE}exec 2>&1OUTPUTS_REDIRECTED="true"elseecho "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"exit 1;fi}
function restoreStandardOutputs {if [ "$OUTPUTS_REDIRECTED" == "true" ]; thenexec 1>&3   #restore stdoutexec 2>&4   #restore stderrOUTPUTS_REDIRECTED="false"fi}
LOGFILE_NAME="tmp/one.log"OUTPUTS_REDIRECTED="false"
echo "this goes to standard output"redirectOutputsToLogfile $LOGFILE_NAMEecho "this goes to logfile"echo "${LOGFILE_NAME}"restoreStandardOutputsecho "After restore this goes to standard output"

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

例如:

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”)。

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

function myfunc(){[...]}
myfunc &>mylog.log