【问题标题】:How to add timestamp to STDERR redirection如何将时间戳添加到 STDERR 重定向
【发布时间】:2010-12-03 05:19:05
【问题描述】:

我们可以在 bash/ksh 中为 STDERR 重定向添加时间戳吗?

例如myscript.sh 2> 错误日志

我也想在日志上写一个时间戳。

【问题讨论】:

    标签: shell scripting


    【解决方案1】:

    重定向是按顺序进行的。试试这个:

    给定一个脚本 -

    $: 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
    

    【讨论】:

      【解决方案2】:

      如何为剩余的输出加上时间戳,将所有输出重定向到标准输出?

      这个答案结合了上面的一些技术,以及来自 unix stackexchange herehere 的一些技术。 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}"
      }
      

      【讨论】:

        【解决方案3】:

        我对所有当前的解决方案都太懒了...所以我想出了一个新的解决方案(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 将时间戳格式化为最小形式。

        如果stdoutstderr 没有引号(" 或`),它应该像一个魅力。如果有(或可能有)最好使用:

        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

        【讨论】:

          【解决方案4】:

          moreutils 包中的程序 ts 将标准输入通过管道传输到标准输出,并在每行前面加上时间戳。

          为标准输出添加前缀:command | ts
          为 stdout 和 stderr 添加前缀:command 2&gt;&amp;1 | ts

          【讨论】:

          【解决方案5】:

          我更喜欢将记录器编写为脚本中的函数,而不是编写脚本来传输,然后用括号将整个过程发送到其中,如下所示:

           # 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
          

          【讨论】:

            【解决方案6】:

            以为我会加上我的 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
            

            【讨论】:

              【解决方案7】:

              我喜欢那些可移植的 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) . $_'
              

              【讨论】:

                【解决方案8】:

                这个东西:nohup myscript.sh 2> >( while read line; do echo "$(date): ${line}"; done > mystd.err ) /null &

                这样工作,但是当我注销并重新登录到服务器时,它不起作用。即使我的进程(此处为 myscript.sh)仍在运行,mystd.err 也会停止填充 stderr 流。

                有人知道如何找回 mystd.err 文件中丢失的 stderr 吗?

                【讨论】:

                • 它并没有丢失,只是你的新登录会话的新(伪)终端也有一个新的和不同的标准错误。如果您想持久运行程序并将它们重新附加到新终端,请使用 screen 或 tmux 而不是 nohup 进行调查。或者将 stderr 重定向到一个文件,并在新会话中使用 tail -f 该文件。
                【解决方案9】:

                如果你想重定向回标准输出,我发现你必须这样做:

                myscript.sh >> >( while read line; do echo "$(date): ${line}"; done )
                

                不知道为什么我需要在( 前面加上&gt;,所以需要&lt;(,但这就是有效的方法。

                【讨论】:

                • 好东西,简短,完美地完成了工作。只是补充一点,如果你想把它放在一个文件而不是 STDOUT 中,你需要在 'done' 和 ')' 之间插入“>> 文件名”,如下所示: myscript.sh >> >( while read line; do echo "$(date): ${line}"; 完成 >> 日志文件)
                【解决方案10】:
                #!/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>&-
                

                【讨论】:

                  【解决方案11】:

                  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
                  

                  【讨论】:

                  • 一个很好的工作工具,但缺点是devscripts有一个巨大的依赖列表。
                  • 使用apt-get install devscripts --no-install-recommends
                  • annotate-output 的一个潜在问题是它不会在末尾打印出任何未完成的(带有\n)行。
                  【解决方案12】:

                  这是一个使用 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 )
                  

                  【讨论】:

                  • 对于这两种情况,error.log 中都没有任何内容(尽管我使用的是 Cygwin)。这对你有用吗?
                  • 我刚才在Cygwin试了一下,还是不行,但是在Ubuntu上可以正常使用。
                  • 原来如此,刚刚在家里的 Ubuntu 上试了一下。这一定是某种 Cygwin 问题。 +1 以获得更简单但不那么曲折的解决方案:-)
                  • 感谢您的代码。另外:我将它用于标准输出和标准错误:myscript.sh &amp;&gt; &gt;( predate.sh &gt; error.log )
                  【解决方案13】:

                  如果您在谈论每行的最新时间戳,这可能是您在实际脚本中想要做的事情(但如果您无权更改它,请参阅下面的一个漂亮的解决方案) .如果您只想在脚本开始编写之前在自己的行上标记日期,我会使用:

                  ( 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 只是一个测试程序,用于写入具有特定时间间隔的stdoutstderr

                  当您运行该命令时,您实际上会将 "d to stdout" 写入 stderr(但那是您的 TTY 设备或其他任何 stdout 可能在您开始时使用的设备)。带时间戳的stderr 行将写入您所需的文件。

                  【讨论】:

                  • 为什么需要交换输出流?
                  • @Cameron:因为管道 | 在标准 输出上运行, 而 OP 希望在标准 错误上进行操作。
                  • 但是( date 1&gt;&amp;2 ; myscript.sh ) 2&gt;error.log 将日期添加到error.log,无论myscript.sh 是否产生任何错误。有没有办法只在myscript.sh产生错误时添加日期?
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-01-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多