在运行使用带有tee的exec将stdout输出发送到终端和文件的脚本后,shell提示似乎不再出现

时间:2014-01-24 09:42:21

标签: linux bash shell

我有一个shell脚本,它将所有输出写入logfile 和终端,这部分工作正常,但如果我执行脚本 只有按Enter键才会出现新的shell提示符。为什么这样,我该如何解决?

#!/bin/bash

exec > >(tee logfile)
echo "output"

4 个答案:

答案 0 :(得分:2)

首先,当我测试这个时,总是 一个新的shell提示符,只是有时字符串output在它之后,所以提示不是最后一个。你碰巧忽略了吗?如果是这样,那么shell似乎会在后台tee完成之前打印提示。

不幸的是,wait的shell tee无法解决这个问题,请参阅unix.stackexchange上的this question。除了脆弱的解决方法之外,我看到解决这个问题的最简单方法是将整个脚本放在列表中:

{
your-code-here
} | tee logfile

答案 1 :(得分:1)

如果我运行以下脚本(从echo抑制换行),我会看到提示,但不是“输出”。该字符串仍然写入文件。

#!/bin/bash

exec > >(tee logfile)
echo -n "output"

我怀疑是这样的:你有三个不同的文件描述符试图写入同一个文件(即终端):shell的标准输出,shell的标准错误,以及{{1的标准输出}}。 shell同步写入:首先是tee到标准输出,然后是标准错误的提示,因此终端能够正确地对它们进行排序。但是,第三个文件描述符由echo异步写入,因此存在竞争条件。我不太明白我的修改如何影响比赛,但它似乎打乱了一些平衡,允许提示在不同的时间写入并出现在屏幕上。 (我希望输出缓冲能够发挥作用)。

您也可以在运行tee命令后尝试运行脚本,该命令将记录写入终端的所有内容;如果你浏览文件中的所有控制字符,你可能会在script写的输出之前就注意到文件中的提示。为了支持我的竞争条件理论,我会注意到在运行脚本几次后,它不再显示“异常”行为;我的shell提示符在字符串“output”之后按预期显示,因此这种情况肯定存在一些非确定性元素。

答案 2 :(得分:0)

@ chepner的回答提供了很好的背景信息。

这是一个解决方法 - 适用于Ubuntu 12.04(Linux 3.2.0)和OS X 10.9.1:

#!/bin/bash

exec > >(tee logfile)
echo "output"

  # WORKAROUND - place LAST in your script.
  # Execute an executable (as opposed to a builtin) that outputs *something*
  # to make the prompt reappear normally.
  # In this case we use the printf *executable* to output an *empty string*.
  # Use of `$ec` is to ensure that the script's actual exit code is passed through.
ec=$?; $(which printf) ''; exit $ec

<强>备选方案:

@ user2719058的答案显示了一个简单的替代方法:将整个脚本体包装在一个组命令({ ... })中并将其传送到tee logfile

正如@chepner已经暗示的那样,外部解决方案是使用script实用程序创建脚本输出的“脚本”以及显示它:

script -qc yourScript /dev/null > logfile   # Linux syntax

然而,这也将捕获 stderr 输出;如果你想避免这种情况,请使用:

script -qc 'yourScript 2>/dev/null' /dev/null > logfile

但请注意,这将完全抑制stderr输出。

答案 3 :(得分:0)

正如其他人所指出的,并不是没有提示打印出来,而是tee编写的输出的最后一个可以在提示之后 出现,从而使该提示不再可见

如果您使用的bash 4.4或更高版本,则可以wait退出tee进程,如下所示:

#!/usr/bin/env bash
case $BASH_VERSION in ''|[0-3].*|4.[0-3]) echo "ERROR: Bash 4.4+ needed" >&2; exit 1;; esac

exec {orig_stdout}>&1 {orig_stderr}>&2       # make a backup of original stdout
exec > >(tee -a "_install_log"); tee_pid=$!  # track PID of tee after starting it

cleanup() {             # define a function we'll call during shutdown
  retval=$?
  exec >&$orig_stdout  # Copy your original stdout back to FD 1, overwriting the pipe to tee
  exec 2>&$orig_stderr # If something overwrites stderr to also go through tee, fix that too
  wait "$tee_pid"      # Now, wait until tee exits
  exit "$retval"       # and complete exit with our original exit status
}
trap cleanup EXIT       # configure the function above to be called during cleanup

echo "Writing something to stdout here"