Redirect stderr and stdout in Bash
我想将进程的stdout和stderr都重定向到单个文件。在巴什我该怎么做?
看看这里。应该是:
1 | yourcommand &>filename |
(将
1 | do_something 2>&1 | tee -a some_file |
这将把stderr重定向到stdout,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 |
注:订单事项如liw.fi所指出,
奇怪的是,这项工作:
1 | yourcommand &> filename |
但这会产生语法错误:
1 2 | yourcommand &>> filename syntax error near unexpected token `>' |
你必须使用:
1 | yourcommand 1>> filename 2>&1 |
简短回答:
说明:
考虑下面的代码,它将单词"stdout"打印到stdout,将单词"stderror"打印到stderror。
1 2 3 | $ (echo"stdout"; echo"stderror">&2) stdout stderror |
请注意,"&;"运算符告诉bash 2是文件描述符(指向stderr),而不是文件名。如果省略"&;",此命令将把
通过试验上面的代码,您可以自己确切地看到重定向操作符是如何工作的。例如,通过更改将两个描述符中的哪一个重定向到
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首先看到
要测试您是否真正了解这个概念,请尝试在切换重定向顺序时猜测输出:
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 ,会出现"无效的空命令"错误。