繁体   English   中英

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

[英]shell prompt seemingly does not reappear after running a script that uses exec with tee to send stdout output to both the terminal and a file

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

#!/bin/bash

exec > >(tee logfile)
echo "output"

首先,当我测试这个的时候,总是一个新的 shell 提示,只是有时字符串output在它后面,所以提示不是最后一个。 是不是你忽略了? 如果是这样,似乎存在一种竞争,即 shell 在后台的tee完成之前打印提示。

不幸的是,这不能通过在 shell 中wait tee ,请参阅 unix.stackexchange 上的这个问题 撇开脆弱的解决方法不谈,我认为解决这个问题的最简单方法是将整个脚本放在一个列表中:

{
your-code-here
} | tee logfile

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

#!/bin/bash

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

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

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

@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 输出。

正如其他人所指出的,并不是没有打印提示——而是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"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM