简体   繁体   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

I have a shell script which writes all output to logfile and terminal, this part works fine, but if I execute the script a new shell prompt only appear if I press enter.我有一个 shell 脚本,它将所有输出写入日志文件和终端,这部分工作正常,但是如果我执行脚本,只有按 Enter 时才会出现新的 shell 提示。 Why is that and how do I fix it?为什么会这样,我该如何解决?

#!/bin/bash

exec > >(tee logfile)
echo "output"

First, when I'm testing this, there always is a new shell prompt, it's just that sometimes the string output comes after it, so the prompt isn't last.首先,当我测试这个的时候,总是一个新的 shell 提示,只是有时字符串output在它后面,所以提示不是最后一个。 Did you happen to overlook it?是不是你忽略了? If so, there seems to be a race where the shell prints the prompt before the tee in the background completes.如果是这样,似乎存在一种竞争,即 shell 在后台的tee完成之前打印提示。

Unfortunately, that cannot fixed by wait ing in the shell for tee , see this question on unix.stackexchange.不幸的是,这不能通过在 shell 中wait tee ,请参阅 unix.stackexchange 上的这个问题 Fragile workarounds aside, the easiest way to solve this that I see is to put your whole script inside a list:撇开脆弱的解决方法不谈,我认为解决这个问题的最简单方法是将整个脚本放在一个列表中:

{
your-code-here
} | tee logfile

If I run the following script (suppressing the newline from the echo ), I see the prompt, but not "output".如果我运行以下脚本(从echo抑制换行符),我会看到提示,但看不到“输出”。 The string is still written to the file.该字符串仍会写入文件。

#!/bin/bash

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

What I suspect is this: you have three different file descriptors trying to write to the same file (that is, the terminal): standard output of the shell, standard error of the shell, and the standard output of tee .我怀疑的是:您有三个不同的文件描述符试图写入同一个文件(即终端):shell 的标准输出、shell 的标准错误和tee的标准输出。 The shell writes synchronously: first the echo to standard output, then the prompt to standard error, so the terminal is able to sequence them correctly. shell 同步写入:首先将echo发送到标准输出,然后提示标准错误,因此终端能够正确地对它们进行排序。 However, the third file descriptor is written to asynchronously by tee , so there is a race condition.但是,第三个文件描述符由tee异步写入,因此存在竞争条件。 I don't quite understand how my modification affects the race, but it appears to upset some balance, allowing the prompt to be written at a different time and appear on the screen.我不太明白我的修改如何影响比赛,但它似乎打破了一些平衡,允许在不同的时间写入提示并出现在屏幕上。 (I expect output buffering to play a part in this). (我希望输出缓冲在其中发挥作用)。

You might also try running your script after running the script command, which will log everything written to the terminal;您也可以尝试在运行script命令后运行您的script ,这将记录写入终端的所有内容; if you wade through all the control characters in the file, you may notice the prompt in the file just prior to the output written by tee .如果您遍历文件中的所有控制字符,您可能会注意到文件中的提示就在tee写入的输出之前。 In support of my race condition theory, I'll note that after running the script a few times, it was no longer displaying "abnormal" behavior;为了支持我的竞争条件理论,我会注意到在运行脚本几次后,它不再显示“异常”行为; my shell prompt was displayed as expected after the string "output", so there is definitely some non-deterministic element to this situation.我的 shell 提示在字符串“输出”之后按预期显示,因此这种情况肯定存在一些不确定因素。

@chepner's answer provides great background information. @chepner 的回答提供了很好的背景信息。

Here's a workaround - works on Ubuntu 12.04 (Linux 3.2.0) and on OS X 10.9.1:这是一个解决方法- 适用于 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

Alternatives:备择方案:

@user2719058's answer shows a simple alternative: wrapping the entire script body in a group command ( { ... } ) and piping it to tee logfile . @ user2719058 的回答显示了一个简单的替代方法:将整个脚本主体包装在一个组命令 ( { ... } ) 中,并将其通过管道传送到tee logfile

An external solution, as @chepner has already hinted at, is to use the script utility to create a "transcript" of your script's output in addition to displaying it:正如@chepner 已经暗示的那样,一个外部解决方案是使用script实用程序来创建脚本输出的“成绩单”以及显示它:

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

This, however, will also capture stderr output;但是,这也将捕获stderr输出; if you wanted to avoid that, use:如果您想避免这种情况,请使用:

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

Note, however, that this will suppress stderr output altogether.但是请注意,这将完全抑制 stderr 输出。

As others have noted, it's not that there's no prompt printed -- it's that the last of the output written by tee can come after the prompt, making the prompt no longer visible.正如其他人所指出的,并不是没有打印提示——而是tee写入的最后一个输出可以出现提示之后,使提示不再可见。

If you have bash 4.4 or newer, you can wait for your tee process to exit, like so:如果您有 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