【发布时间】:2010-12-03 05:19:05
【问题描述】:
我们可以在 bash/ksh 中为 STDERR 重定向添加时间戳吗?
例如myscript.sh 2> 错误日志
我也想在日志上写一个时间戳。
【问题讨论】:
我们可以在 bash/ksh 中为 STDERR 重定向添加时间戳吗?
例如myscript.sh 2> 错误日志
我也想在日志上写一个时间戳。
【问题讨论】:
重定向是按顺序进行的。试试这个:
给定一个脚本 -
$: cat tst
echo a
sleep 2
echo 1 >&2
echo b
sleep 2
echo 2 >&2
echo c
sleep 2
echo 3 >&2
echo d
我得到以下信息
$: ./tst 2>&1 1>stdout | sed 's/^/echo $(date +%Y%m%dT%H%M%S) /; e'
20180925T084008 1
20180925T084010 2
20180925T084012 3
尽管我不喜欢 awk,但它确实避免了迄今为止的冗余子调用。
$: ./tst 2>&1 1>stdout | awk "{ print strftime(\"%Y%m%dT%H%M%S \") \$0; fflush() }"; >stderr
$: cat stderr
20180925T084414 1
20180925T084416 2
20180925T084418 3
$: cat stdout
a
b
c
d
【讨论】:
如何为剩余的输出加上时间戳,将所有输出重定向到标准输出?
这个答案结合了上面的一些技术,以及来自 unix stackexchange here 和 here 的一些技术。 bash >= 4.2 是假设的,但其他高级 shell 也可以工作。对于 4.2,将printf 替换为对date 的(较慢)调用。
: ${TIMESTAMP_FORMAT:="%F %T"} # override via environment
_loglines() {
while IFS= read -r _line ; do
printf "%(${TIMESTAMP_FORMAT})T#%s\n" '-1' "$_line";
done;
}
exec 7<&2 6<&1
exec &> >( _loglines )
# Logit
恢复标准输出/标准错误:
exec 1>&6 2>&7
然后您可以使用tee 将时间戳发送到stdout和一个日志文件。
_tmpfile=$(mktemp)
exec &> >( _loglines | tee $_tmpfile )
如果进程没有错误退出,那么有清理代码是个好主意:
trap "_cleanup \$?" 0 SIGHUP SIGINT SIGABRT SIGBUS SIGQUIT SIGTRAP SIGUSR1 SIGUSR2 SIGTERM
_cleanup() {
exec >&6 2>&7
[[ "$1" != 0 ]] && cat "$_logtemp"
rm -f "$_logtemp"
exit "${1:-0}"
}
【讨论】:
我对所有当前的解决方案都太懒了...所以我想出了一个新的解决方案(stdout 的工作也可以调整为stderr):
echo "output" | xargs -L1 -I{} bash -c "echo \$(date +'%x %T') '{}'" | tee error.log
将保存到文件并打印类似的内容:
11/3/16 16:07:52 output
详情:
-L1 表示“换行”
-I{} 表示“用输入替换 {}”
bash -c 用于每次更新$(date) 以获取新呼叫
%x %T 将时间戳格式化为最小形式。
如果stdout 和stderr 没有引号(" 或`),它应该像一个魅力。如果有(或可能有)最好使用:
echo "output" | awk '{cmd="(date +'%H:%M:%S')"; cmd | getline d; print d,$0; close(cmd)} | tee error.log'
(取自我在另一个话题中的回答:https://stackoverflow.com/a/41138870/868947)
【讨论】:
moreutils 包中的程序 ts 将标准输入通过管道传输到标准输出,并在每行前面加上时间戳。
为标准输出添加前缀:command | ts
为 stdout 和 stderr 添加前缀:command 2>&1 | ts
【讨论】:
我更喜欢将记录器编写为脚本中的函数,而不是编写脚本来传输,然后用括号将整个过程发送到其中,如下所示:
# Vars
logfile=/path/to/scriptoutput.log
# Defined functions
teelogger(){
log=$1
while read line ; do
print "$(date +"%x %T") :: $line" | tee -a $log
done
}
# Start process
{
echo 'well'
sleep 3
echo 'hi'
sleep 3
echo 'there'
sleep 3
echo 'sailor'
} | teelogger $logfile
【讨论】:
以为我会加上我的 2 美分的价值..
#!/bin/sh
timestamp(){
name=$(printf "$1%*s" `expr 15 - ${#1}`)
awk "{ print strftime(\"%b %d %H:%M:%S\"), \"- $name -\", $$, \"- INFO -\", \$0; fflush() }";
}
echo "hi" | timestamp "process name" >> /tmp/proccess.log
printf "$1%*s" `expr 15 - ${#1}`
将名称隔开,使其看起来不错,其中 15 是发出的最大空间,如果需要,可以增加
输出 >> 日期 - 进程名称 - 进程 ID - 信息 - 消息
Jun 27 13:57:20 - process name - 18866 - INFO - hi
【讨论】:
我喜欢那些可移植的 shell 脚本,但有点不安,因为它们每行都 fork/exec date(1)。这是一个快速的 perl 单行程序,可以更有效地执行相同操作:
perl -p -MPOSIX -e 'BEGIN {$!=1} $_ = strftime("%T ", localtime) . $_'
要使用它,只需通过它的标准输入输入这个命令:
(echo hi; sleep 1; echo lo) | perl -p -MPOSIX -e 'BEGIN {$|=1} $_ = strftime("%T ", localtime) . $_'
【讨论】:
这个东西:nohup myscript.sh 2> >( while read line; do echo "$(date): ${line}"; done > mystd.err ) /null &
这样工作,但是当我注销并重新登录到服务器时,它不起作用。即使我的进程(此处为 myscript.sh)仍在运行,mystd.err 也会停止填充 stderr 流。
有人知道如何找回 mystd.err 文件中丢失的 stderr 吗?
【讨论】:
如果你想重定向回标准输出,我发现你必须这样做:
myscript.sh >> >( while read line; do echo "$(date): ${line}"; done )
不知道为什么我需要在( 前面加上>,所以需要<(,但这就是有效的方法。
【讨论】:
#!/bin/bash
DEBUG=1
LOG=$HOME/script_${0##*/}_$(date +%Y.%m.%d-%H.%M.%S-%N).log
ERROR=$HOME/error.log
exec 2> $ERROR
exec 1> >(tee -ai $LOG)
if [ $DEBUG = 0 ] ; then
exec 4> >(xargs -i echo -e "[ debug ] {}")
else
exec 4> /dev/null
fi
# test
echo " debug sth " >&4
echo " log sth normal "
type -f this_is_error
echo " errot sth ..." >&2
echo " finish ..." >&2>&4
# close descriptor 4
exec 4>&-
【讨论】:
Debian/Ubuntu 中的 devscripts 软件包包含一个名为 annotate-output 的脚本,它可以执行此操作(对于 stdout 和 stderr)。
$ annotate-output make
21:41:21 I: Started make
21:41:21 O: gcc -Wall program.c
21:43:18 E: program.c: Couldn't compile, and took me ages to find out
21:43:19 E: collect2: ld returned 1 exit status
21:43:19 E: make: *** [all] Error 1
21:43:19 I: Finished with exitcode 2
【讨论】:
apt-get install devscripts --no-install-recommends
这是一个使用 while read 循环的版本,如 pax's,但不需要额外的文件描述符或单独的脚本(尽管您可以使用一个)。它使用进程替换:
myscript.sh 2> >( while read line; do echo "$(date): ${line}"; done > error.log )
使用 pax's predate.sh:
myscript.sh 2> >( predate.sh > error.log )
【讨论】:
myscript.sh &> >( predate.sh > error.log )
如果您在谈论每行的最新时间戳,这可能是您在实际脚本中想要做的事情(但如果您无权更改它,请参阅下面的一个漂亮的解决方案) .如果您只想在脚本开始编写之前在自己的行上标记日期,我会使用:
( date 1>&2 ; myscript.sh ) 2>error.log
您需要一个技巧,通过另一个可以为每一行添加时间戳的程序来管道 stderr。您可以使用 C 程序执行此操作,但仅使用 bash 有一种更狡猾的方法。
首先,创建一个脚本,将时间戳添加到每一行(称为predate.sh):
#!/bin/bash
while read line ; do
echo "$(date): ${line}"
done
例如:
( echo a ; sleep 5 ; echo b ; sleep 2 ; echo c ) | ./predate.sh
产生:
Fri Oct 2 12:31:39 WAST 2009: a
Fri Oct 2 12:31:44 WAST 2009: b
Fri Oct 2 12:31:46 WAST 2009: c
那么你需要另一个技巧,可以交换标准输出和标准错误,这个小怪物在这里:
( myscript.sh 3>&1 1>&2- 2>&3- )
然后通过时间戳stdout 并将其重定向到您的文件来组合这两个技巧很简单:
( myscript.sh 3>&1 1>&2- 2>&3- ) | ./predate.sh >error.log
以下记录显示了这一点:
pax> cat predate.sh
#!/bin/bash
while read line ; do
echo "$(date): ${line}"
done
pax> cat tstdate.sh
#!/bin/bash
echo a to stderr then wait five seconds 1>&2
sleep 5
echo b to stderr then wait two seconds 1>&2
sleep 2
echo c to stderr 1>&2
echo d to stdout
pax> ( ( ./tstdate.sh ) 3>&1 1>&2- 2>&3- ) | ./predate.sh >error.log
d to stdout
pax> cat error.log
Fri Oct 2 12:49:40 WAST 2009: a to stderr then wait five seconds
Fri Oct 2 12:49:45 WAST 2009: b to stderr then wait two seconds
Fri Oct 2 12:49:47 WAST 2009: c to stderr
如前所述,predate.sh 将为每一行添加时间戳前缀,tstdate.sh 只是一个测试程序,用于写入具有特定时间间隔的stdout 和stderr。
当您运行该命令时,您实际上会将 "d to stdout" 写入 stderr(但那是您的 TTY 设备或其他任何 stdout 可能在您开始时使用的设备)。带时间戳的stderr 行将写入您所需的文件。
【讨论】:
| 在标准 输出上运行, 而 OP 希望在标准 错误上进行操作。
( date 1>&2 ; myscript.sh ) 2>error.log 将日期添加到error.log,无论myscript.sh 是否产生任何错误。有没有办法只在myscript.sh产生错误时添加日期?