【问题标题】:Redirect stdout and stderr to Function将 stdout 和 stderr 重定向到 Function
【发布时间】:2012-08-07 22:43:54
【问题描述】:

我需要帮助将系统命令的输出(stdin 和 stdout)发送到 bash 函数,同时仍接受来自参数的输入。类似于下面的示例。有人能指出我正确的道路吗?

LogMsg()
{
  DateTime=`date "+%Y/%m/%d %H:%M:%S"`
  echo '*****'$DateTime' ('$QMAKESPEC'): '$1 >> "$LogFile"
  echo $DateTime' ('$QMAKESPEC'): '$1
}

# Already works
LogMsg "This statement is sent directly"

# Wish I could do this:
# Capture both stdout & stderr of a system function to the logfile
# I do not presume that any of the syntax that follows is good
make 2>&1 >(LogMsg)

【问题讨论】:

  • 您还希望能够使用命令行参数调用LogMsg吗?

标签: bash


【解决方案1】:

有两种方法, 首先,我认为更好的是创建一个 bash 文件并将结果传递给它,如下所示:

make 2>&1 > ./LogMsg

第二种方法是将结果作为参数传递给函数:

LogMsg $(make 2>&1)

【讨论】:

  • 您的第一个选项不清楚。您的意思是将 make 的输出通过管道传输到LogMsg (无法从标准输入中读取)?您的第二个选项将只处理 make 的第一行,因为 LogMsg 只处理它的第一个参数。
【解决方案2】:

为此,您可以使用read bash 内置函数:

LogMsg()
{
  read IN # This reads a string from stdin and stores it in a variable called IN
  DateTime=`date "+%Y/%m/%d %H:%M:%S"`
  echo '*****'$DateTime' ('$QMAKESPEC'): '$IN >> "$LogFile"
  echo $DateTime' ('$QMAKESPEC'): '$IN
}

然后使用管道:

make 2>&1 | LogMsg

更新:

为了能够使用标准输入或参数作为输入(根据 chepner 的评论),您可以这样做:

LogMsg()
{
  if [ -n "$1" ]
  then
      IN="$1"
  else
      read IN # This reads a string from stdin and stores it in a variable called IN
  fi

  DateTime=`date "+%Y/%m/%d %H:%M:%S"`
  echo '*****'$DateTime' ('$QMAKESPEC'): '$IN >> "$LogFile"
  echo $DateTime' ('$QMAKESPEC'): '$IN
}

【讨论】:

  • 这种方法的唯一问题是你不能在没有标准输入的情况下调用 LogMsg。目前尚不清楚 Ryan 是否想要这种灵活性。
  • 是的,需要灵活性,我应该更清楚。
  • 你希望能够both,即echo foo | LogMsg baz吗?我一直无法想出一个可以同时处理一个、另一个或两者的解决方案。
  • 捕获两个字符串很容易。但目前尚不清楚您将如何处理它们。一个接一个地打印?
  • 不,现在是非此即彼的情况。我希望能够捕获显式参数或捕获 STDOUT 和 STDERR。
【解决方案3】:

在我看来,读取命令中的 100 毫秒 (-t 0.1) 超时将允许 LogMsg 处理输入管道和参数,而无需在没有输入的情况下永远等待。

function log(){ read -t 0.1 IN1
  echo $(date "+%Y/%m/%d %H:%M:%S")' ('$QMAKESPEC'): '$IN1 $* |tee -a $LogFile ;}
#test without, with pipe , with pipe and parameters , with parameters only
log ; echo foo | log ; echo foo | log bar ; log bar
2015/01/01 16:52:17 ():
2015/01/01 16:52:17 (): foo
2015/01/01 16:52:17 (): foo bar
2015/01/01 16:52:17 (): bar

tee -a 复制到标准输出并附加到 $LogFile

玩得开心

【讨论】:

    【解决方案4】:

    这是一个旧线程.. 但我用它来帮助我编写一个日志函数,该函数还将输出多行命令输出:

    # Defines function to grab a time stamp #
    get_Time () { Time=$(date +%Y-%m-%d\ %H:%M:%S) ; }
    
    write_Log()
    {
    get_Time
    if [ -n "${1}" ]; then         # If it's from a "<message>" then set it
        IN="${1}"
        echo "${Time} ${IN}" | tee -a ${log_File}
    else
        while read IN               # If it is output from command then loop it
        do
            echo "${Time} ${IN}" | tee -a ${log_File}
        done
    fi
    }
    

    【讨论】:

      【解决方案5】:

      感谢发布回复的人。我想出了我的版本,它将为每条消息添加一次时间戳。

      #!/bin/bash
      CURRENT_PID=$$
      PROCESS_NAME=$(basename $0)
      
      LOGFILE=/var/log/backup-monitor.log
      function log_message {
        if [ -n "$1" ]; then
            MESSAGE="$1"
            echo -e "$(date -Iseconds)\t$PROCESS_NAME\t$CURRENT_PID\t$MESSAGE" | tee -a $LOGFILE
        else
            MESSAGE=$(tee)
            echo -e "$(date -Iseconds)\t$PROCESS_NAME\t$CURRENT_PID\t$MESSAGE" | tee -a $LOGFILE
        fi
      }
      
      log_message "Direct arguments are working!!"
      
      echo "stdin also working" | log_message
      

      【讨论】:

        【解决方案6】:

        根据之前的答案,我整理了一些可以使用或不使用日志文件的通用函数,如本文末尾所列。这些对于更复杂的脚本很方便。我通常将终端窗口消息打印到stderr,以免干扰可能需要重定向的合法程序输出。函数调用如下:

        scriptFolder=$(cd $(dirname "$0") && pwd)
        scriptName=$(basename $scriptFolder)
        # Start a log file that will be used by the logging functions
        logFileStart ${scriptName} "${scriptFolder)/${scriptName}.log"
        
        # The following logs the message string passed to the function.
        # - use a space for empty lines because otherwise the logging function
        #   will hang waiting for input
        logInfo " "
        logInfo "Starting to do some work."
        
        # The following will log each 'stdout` and `stderr` line piped to the function.
        someOtherProgram 2>&1 | logInfo
        

        函数...

        # Echo to stderr
        echoStderr() {
          # - if necessary, quote the string to be printed
          # - redirect stdout from echo to stderr
          echo "$@" 1>&2
          # Or, use an alternate echo such one that colors textT
          # ${echo2} "$@" 1>&2
        }
        
        # Print a DEBUG message
        # - prints to stderr and optionally appends to log file if ${logFile} is defined globally
        #   - see logFileStart() to start a log file
        # - call with parameters or pipe stdout and stderr to this function: 2>&1 | logDebug
        # - print empty lines with a space " " to avoid hanging the program waiting on stdin input
        logDebug() {
          if [ -n "${1}" ]; then
            if [ -n "${logFile}" ]; then
              # Are using a log file
              echoStderr "[DEBUG] $@" 2>&1 | tee --append $logFile
            else
              # Are NOT using a log file
              echoStderr "[DEBUG] $@"
            fi
          else
            while read inputLine; do
              if [ -n "${logFile}" ]; then
                # Are using a log file
                echoStderr "[DEBUG] ${inputLine}" 2>&1 | tee --append $logFile
              else
                # Are NOT using a log file
                echoStderr "[DEBUG] ${inputLine}"
              fi
            done
          fi
        }
        
        # Print an ERROR message
        # - prints to stderr and optionally appends to log file if ${logFile} is defined globally
        #   - see logFileStart() to start a log file
        # - call with parameters or pipe stdout and stderr to this function: 2>&1 | logError
        # - print empty lines with a space " " to avoid hanging the program waiting on stdin input
        logError() {
          if [ -n "${1}" ]; then
            if [ -n "${logFile}" ]; then
              # Are using a log file
              echoStderr "[ERROR] $@" 2>&1 | tee --append $logFile
            else
              # Are NOT using a log file
              echoStderr "[ERROR] $@"
            fi
          else
            while read inputLine; do
              if [ -n "${logFile}" ]; then
                # Are using a log file
                echoStderr "[ERROR] ${inputLine}" 2>&1 | tee --append $logFile
              else
                # Are NOT using a log file
                echoStderr "[ERROR] ${inputLine}"
              fi
            done
          fi
        }
        
        # Start a new logfile
        # - name of program that is being run is the first argument
        # - path to the logfile is the second argument
        # - echo a line to the log file to (re)start
        # - subsequent writes to the file using log*() functions will append
        # - the global variable ${logFile} will be set for use by log*() functions
        logFileStart() {
          local newLogFile now programBeingLogged
          programBeingLogged=$1
          # Set the global logfile, in case it was not saved
          if [ -n "${2}" ]; then
            logFile=${2}
          else
            # Set the logFile to stderr if not specified, so it is handled somehow
            logFile=/dev/stderr
          fi
          now=$(date '+%Y-%m-%d %H:%M:%S')
          # Can't use logInfo because it only appends and want to restart the file
          echo "Log file for ${programBeingLogged} started at ${now}" > ${logFile}
        }
        
        # Print an INFO message
        # - prints to stderr and optionally appends to log file if ${logFile} is defined globally
        #   - see logFileStart() to start a log file
        # - call with parameters or pipe stdout and stderr to this function: 2>&1 | logInfo
        # - print empty lines with a space " " to avoid hanging the program waiting on stdin input
        logInfo() {
          if [ -n "${1}" ]; then
            if [ -n "${logFile}" ]; then
              # Are using a log file
              echoStderr "[INFO] $@" 2>&1 | tee --append $logFile
            else
              # Are NOT using a log file
              echoStderr "[INFO] $@"
            fi
          else
            while read inputLine; do
              if [ -n "${logFile}" ]; then
                # Are using a log file
                echoStderr "[INFO] ${inputLine}" 2>&1 | tee --append $logFile
              else
                # Are NOT using a log file
                echoStderr "[INFO] ${inputLine}"
              fi
            done
          fi
        }
        
        # Print an WARNING message
        # - prints to stderr and optionally appends to log file if ${logFile} is defined globally
        #   - see logFileStart() to start a log file
        # - call with parameters or pipe stdout and stderr to this function: 2>&1 | logWarning
        # - print empty lines with a space " " to avoid hanging the program waiting on stdin input
        logWarning() {
          if [ -n "${1}" ]; then
            if [ -n "${logFile}" ]; then
              # Are using a log file
              echoStderr "[WARNING] $@" 2>&1 | tee --append $logFile
            else
              # Are NOT using a log file
              echoStderr "[WARNING] $@"
            fi
          else
            while read inputLine; do
              if [ -n "${logFile}" ]; then
                # Are using a log file
                echoStderr "[WARNING] ${inputLine}" 2>&1 | tee --append $logFile
              else
                # Are NOT using a log file
                echoStderr "[WARNING] ${inputLine}"
              fi
            done
          fi
        }
        

        【讨论】:

          猜你喜欢
          • 2014-07-22
          • 1970-01-01
          • 2022-01-18
          • 2011-05-23
          • 2020-06-02
          • 2011-07-22
          • 2012-07-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多