如何使 bash 脚本输出与终端和文件兼容?

如何使 bash 脚本输出与终端和文件兼容?

在 bash 中,如果我想生成定期的状态消息,我可以通过发出热键组合来替换前一个消息,例如这里这里或者这里。当用户直接执行脚本时,这很好,因为它可以让他们随时了解情况,而不会使他们的屏幕变得混乱。

但是,如果他们想在自动化环境中使用相同的脚本,则输出将(必须)重定向到文件中:

./test.sh >log.txt

此时,\r\b\033[0K不起作用。

我如何才能让这两种模式都发挥作用?

(例如,直接省略中间消息就足够了,当且仅当我可以检测输出流是否是一个文件。)

  • 如果可能的话,我希望避免在脚本中引入参数。实际版本已经附带了几个参数。
  • 如果可能的话,我想避免处理脚本本身内部的输出流,例如这个问题,因此当嵌入到其他脚本中时,它不会表现出令人惊讶的行为。

示例代码test.sh

#!/bin/bash

PREVIOUS_JOB_STATUS=""
SECONDS=0          # auto-incremented by Bash; divide by INTERVAL_SECONDS to get number of retries
INTERVAL_SECONDS=3 # interval between polls to the server
TIMEOUT_SECONDS=10 # how long until we give up, because the server is clearly busy (or dead)
while true; do
  RETRY_SECONDS=$SECONDS
  if [ $RETRY_SECONDS -lt $INTERVAL_SECONDS ]; then # in the real world, here would be      the polling operation
    JOB_STATUS='queued'
  else
    JOB_STATUS='processing'
  fi
  RESULT=1                                          # in the real world, here would be ? of the polling operation
  if [ $RESULT -eq 0 ]; then                            # success
    exit $RESULT
  elif [ $RETRY_SECONDS -gt $TIMEOUT_SECONDS ]; then    # failure (or at least surrender)
    echo "Timeout exceeded waiting for job to finish. Current Job Status is '$JOB_STATUS'. Current result code is '$RESULT'."
    exit $RESULT
  elif [ "$PREVIOUS_JOB_STATUS" = "$JOB_STATUS" ]; then # no change yet,      replace last status message and try again
    echo -en '\033[1A\r\033[K' # move one line up                                : \033[1A ,
                               # move to pos1                                    : \r      ,
                               # delete everything between cursor and end-of-line: \033[K  ,
                               # omit line break                                 : -n
  else                                                  # job status changed, write   new  status message and try again
    PREVIOUS_JOB_STATUS=$JOB_STATUS
  fi
  SLEEP_MESSAGE='Waiting for job to finish. Waited '`printf "%02g" $SECONDS`" s. Current job status is '$JOB_STATUS'. Current result code is '$RESULT'."
  echo $SLEEP_MESSAGE
  sleep $INTERVAL_SECONDS
done

最终输出./test.sh

Waiting for job to finish. Waited 00 s. Current job status is 'queued'. Current result code is '1'.
Waiting for job to finish. Waited 09 s. Current job status is 'processing'. Current result code is '1'.
Timeout exceeded waiting for job to finish. Current Job Status is 'processing'. Current result code is '1'.

最终输出./test.sh >log.txt 2>&1

Waiting for job to finish. Waited 00 s. Current job status is 'queued'. Current result code is '1'.
Waiting for job to finish. Waited 03 s. Current job status is 'processing'. Current result code is '1'.
^[[1A^M^[[KWaiting for job to finish. Waited 06 s. Current job status is 'processing'. Current result code is '1'.
^[[1A^M^[[KWaiting for job to finish. Waited 09 s. Current job status is 'processing'. Current result code is '1'.
Timeout exceeded waiting for job to finish. Current Job Status is 'processing'. Current result code is '1'.

期望最终产量./test.sh >log.txt 2>&1:等于最终产量./test.sh

答案1

使用test -t 1test -t 2测试 stdout 或 stderr 是否分别与终端相关联。

-t file_descriptor
如果文件描述符 file_descriptor 已打开并且与终端关联,则为 True。如果文件描述符file_descriptor不是有效的文件描述符,或者文件描述符file_descriptor未打开,或者已打开但未与终端关联,则为 False。

来源

概念证明:

sh -c '
test -t 1 && echo foo 
echo bar
'

按原样运行上述代码,您将看到foobar。将输出从终端重定向(例如重定向到常规文件或通过管道重定向到cat)并且foo不会被打印。

为了避免每次需要打印中间消息时进行测试,请使用以下技巧:

if test -t 1; then exec 5>&1; else exec 5>/dev/null; fi

现在将每个中间消息打印到文件描述符 5(例如echo baz >&5)。如果是终端,它将转到 stdout,否则转到/dev/null

相关内容