如何对从交互式命令传递到以 tee 结尾的管道中的输出解除缓冲?

Ran*_*vel 6 osx bash pipe buffer homebrew

     我正在对交互式命令进行故障排除并希望:

  • 查看打印到我的屏幕的输出,其原始着色,无缓冲或行缓冲(而不是缓冲,),因为该命令生成它
  • 使用类似的tee方法将这个命令的输出同时重定向到一个文件,保留——也就是说,不乱码(例如通过 raw传递ANSI 转义序列而不是正确处理它们)——它在这个结果日志文件中的颜色。 (我已经意识到这种偏好的一部分可能是不可能的,因此将其替换为以下偏好。)
  • 使用tee或类似的东西同时将此命令的输出重定向到一个文件
  • 在运行时显示由相关命令着色的输出
  • 不要用ANSI 转义序列污染结果日志 ——也就是说,在终端上显示之后但将其保存到日志文件之前,将它们从输出中剥离。

通常,tee -a这对我来说是用我试图像魅力一样在这里登录的同一种命令的输出,但是我在管道中遇到的一些奇怪的极端情况tee正在破坏这种正常的、明智的行为,似乎. 我已经做了一些挖掘,看看以前是否有人遇到过类似的问题并想出了解决方案,但我能够挖掘出的所有相关材料是这样的:

这些资源都没有,但是,相当提供足够的线索,我想我或许能对他们那么快用它来沿着我后一起对我自己的线鹅卵石的东西,我会 -所以我终于可以当然,将我需要帮助生成的日志传递给可以从中收集信息的人。
     我已经在我已经阅读过的内容中尝试了一些不同的基于unbuffer- 和 -script的建议,并且stdbuf在我开始写这个问题之前正在考虑给出一些建议,但还没有开始做最后一个然而。我/usr/bin/bash在 OS X v10.11.6 'El Capitan' 上使用。我正在尝试解决的命令是HOMEBREW_BUILD_FROM_SOURCE=1 brew upgrade -vd --build-from-source mailutils,这里brew家酿包管理器和Mailutils我是从源试图建立并到我想升级为V3.3版本(我现在有V3.2,),但我深深怀疑切换出来使用完全不同的命令,当用作管道的一部分时也会产生块缓冲输出(如果我理解正确,这将是任何shell 命令,正如我所看到的,它提到设置管道可能会导致块缓冲本身)不会在这里解决我的问题。我可以使用什么工具来实现我所寻求的关于我试图在此处捕获的日志的结果?

Eli*_*gan 6

过程替换1允许您script在编写时转换打字稿。

您可以使用而不是编写删除转义序列的日志,其中运行删除它们的脚本的任何命令。在相对较新版本的 macOS 或 FreeBSD 以外的其他系统上,命令会略有不同,因为不同的实现支持不同的选项。请参阅下面的完整详细信息。script -q >(./dewtell >>outfile) brew args...script -aq outfile brew args..../dewtellscript

迄今为止的故事(包括替代解决方案)

您的解决方案

您的brew命令将许多其他输出生成程序作为子进程运行,例如./configure脚本和make,并且您发现管道brew的输出到teewith会导致输出(至少其中一些子进程的)被缓冲并且不会出现在您的终端上实时。brew args... 2>&1 | tee -a outfile

您发现使用script,通过运行script -aq,通过将标准输出保持在终端2解决了这个问题,并且-k如果您希望自己的输入被记录,即使在您键入时它没有回显到终端的情况下,您也可以通过。您进一步发现,dewtell的扩展版本吉尔的Perl脚本从文件中删除转义序列清理有效生成的打字稿,把它变成你所需要的。

Gilles 的原始脚本和 dewtell 的扩展版本之间的区别在于,虽然它们都删除了转义序列,包括但不限于那些指定颜色变化的转义序列,但 dewtell 的脚本还删除了回车字符($'\r',表示为^Minvim和 in 的输出cat -v)为以及退格字符($'\b',表示为^Hinvim和 in 的输出cat -v)以及它们似乎已删除的任何字符(如果有)。

一些 Perl 实现的问题

您报告说该脚本需要一个“相对较新的”Perl 解释器。但它并不需要任何更新的功能use或以其他方式依赖它们,我的一个运行 macOS 10.11.6 El Capitan 的朋友已经验证它可以与系统提供的 perl 5.8.12 一起使用,所以我不不知道为什么(或是否)您需要更新的 perl。我希望大多数人可以只使用他们拥有的 perl。

但是该脚本在 Mac OS X 10.4.11 Tiger (PPC) 和系统提供的 perl 5.8.6 上确实失败了,它错误地认为(至少在我的系统上)m不在字符类中[@-~],即使使用LC_COLLATE=CLC_ALL=C并且即使系统提供的grepsedpython,和ruby没有这个问题。这导致指定颜色的转义序列片段保留在输出中,因为这些序列未​​能匹配(?:\e\[|\x9b) [ -?]* [@-~]并匹配后来的替代方案\e.。使用perlbrew,我在该系统上安装了 perl 5.27.5、5.8.9 甚至 5.6.2;没有人有问题。

如果确实需要或想要使用安装在其他地方的 Perl 解释器运行脚本/usr/bin/perl,则可以将顶部的hashbang行更改为正确的路径;或者可以将其更改为/usr/bin/env perl如果所需的perl可执行文件首先出现在路径中,即,如果键入perl并按下它会运行Enter;或者可以使用脚本的文件名作为其第一个参数显式调用解释器,例如,/usr/local/bin/perl dewtell而不是./dewtell.

保留或替换原始打字稿的方法

一些需要从打字稿中删除转义序列的用户也希望保留旧的未处理的打字稿。如果这样的用户希望处理名为 的打字稿dirty.log并将输出写入clean.log,他们将运行./dewtell dirty.log > clean.log,如有必要,替换./dewtell为任何其他命令运行dewtell 的脚本(或他们希望使用的其他脚本)。

要改为“就地”修改打字稿,可以传递-iperl, running where是由 生成的打字稿,是用于备份文件的任何后缀,是 Perl 脚本。或者可以改为运行,它会丢弃原始文件,因为没有提供备份后缀。这些方法不适用于所有 Perl 脚本,但它们适用于这个脚本,因为它用于读取输入,并且在 Perl 方面perl -i.orig dewtell typescripttypescriptscript.origdewtellperl -i dewtell typescript<><>-i

您过去常常sponge将更改写回原始打字稿。这也是一种很好且可靠的方法,尽管它需要安装moreutils

防止记录转义序列

剩下的问题是如何编写一个首先删除转义序列的日志。正如你所说

[T] 这里可能还有一种方法可以将所有这些组合成一个单一的管道,但在撰写本文时我还没有弄清楚;其他评论或答案显示如何...

使用进程替换而不是管道。

问题是script在大多数(所有?)系统上没有支持这些转换的选项。由于script写入其名称您指定或默认为typescript--not to standard output--piping from 的文件script不会影响写入打字稿的内容。

script命令放在管道运算符 ( |)的右侧以通过管道连接它也不是一个好主意。在您的情况下,这特别是因为brew当其标准输出是管道时,来自或其子进程的输出被缓冲,因此当您需要查看它时它没有出现。

即使这个问题得到了解决,我不知道使用任何合理的方式管道1一起script去完成这个任务。

但它可以通过进程替换来完成。3过程替换1此处也有解释)中,您编写或。壳创建一个命名管道,并使用它作为标准输出或输入,分别为一个子外壳在其中运行。文本or被命名管道的文件名替换——这是替换——因此您可以将其作为参数传递给程序或将其用作重定向的目标。<(command...)>(command...)command...<(command...)>(command...)

  • 使用到运行就像它的输出是你一个文件的内容读出4<(command...)command...
  • 使用运行像它的输入是你一个文件的内容写入4>(command...)command...

并非所有系统都支持命名管道,但大多数都支持。并非所有 shell 都支持进程替换,但 Bash 支持,只要它运行在能够支持它的系统上,您的 Bash 构建就没有省略对它的支持,并且在 shell 中关闭了 POSIX 模式。在 Bash 中,您通常可以访问进程替换,尤其是当您使用任何远程最近的操作系统时。即使我的Mac OS X 10.4.11虎(PPC)系统,其中的"$BASH_VERSION"2.05b.0(1)-release,进程替换工作得很好。

以下是script在最近的 macOS 系统上使用的语法时如何做到这一点。

这应该适用于您的 macOS 10.11 El Capitan 系统——并且,通过该联机帮助页,任何 macOS 系统至少可以追溯到 macOS 10.9 Mavericks甚至更早:

script -q >(./dewtell >>clean.log) brew args...
Run Code Online (Sandbox Code Playgroud)

这会记录写入终端的所有内容,包括您自己的输入,如果它被回显给您,即,如果它出现在终端中,它通常会这样做。如果您希望自己的输入即使没有出现也被记录,请记住,发生这种情况的情况通常是您输入密码,然后正如您在回答中提到,添加-k选项:

script -kq >(./dewtell >>clean.log) brew args...
Run Code Online (Sandbox Code Playgroud)

在任何一种情况下,替换./dewtell为运行dewtell 脚本的任何命令或您想用来过滤输出的任何其他程序或脚本,使用clean.log您希望将打字稿写入的文件名并省略转义序列,并使用您正在使用的命令替换5运行及其参数。brew args...

覆盖或附加到日志

如果你想覆盖clean.log而不是附加到它然后使用而不是. 实际文件由通过进程替换运行的命令写入,因此or重定向运算符出现在.>clean.log>>clean.log>>>>( )

不要尝试使用>>(代替>(,这是一个语法错误并且毫无意义,因为>in >(for 进程替换并不意味着重定向。

千万不要错过-ascript的意图,它会阻止你的日志文件,在这种情况下不被覆盖,因为这将只需打开命名管道追加模式-其中有像打开它正常的写同样的效果-和然后 overwrite 或 append clean.log,仍然取决于是否在子shell中使用或。>clean.log>>clean.log

同样,不要在内部(或任何地方)使用>&&>或添加,因为如果生成任何错误或警告,您会希望看到它们而不是将它们写入. 该命令自动在其打字稿中包含来自标准错误的文本;你不需要做任何特别的事情来实现这一点。2>&1>( )./dewtellclean.logscript

在其他操作系统上

正如你的回答所说:

[S] 某些版本的script命令有不同的语法;给出的是 OS X/macOS,因此请根据需要进行调整。

GNU/Linux

大多数 GNU/Linux 系统使用util-linuxscript提供的实现。如果要使其运行特定命令而不是启动 shell,则必须使用该选项并将整个命令作为单个命令行参数传递给,您可以通过将其括在引号中来实现。这与像您这样的最新 macOS 系统上的版本不同,它允许您将命令作为放置在输出文件名之后的多个参数自然地传递(没有像 那样的选项)。-cscriptscript-c

所以在 Debian、Ubuntu、Fedora、CentOS 和大多数其他 GNU/Linux 系统上,你可以使用这个命令(如果它有一个brew命令6,或者用你想要运行的任何命令替换它并记录转换后的输出):

script >(./dewtell >>clean.log) -qc 'brew args...'
Run Code Online (Sandbox Code Playgroud)

script在您的系统上一样,在 GNU/Linux 上,-q如果您想script包含更多关于日志记录如何开始和结束的消息,请删除。即使有这个-q选项,这个版本script的顶部仍然包含一行,说明它何时开始运行,尽管它不会向显示该行,也不会写入或显示有关它何时停止运行的任何信息。

没有-k选择。只记录出现在终端中的文本。7

FreeBSD

scriptmacOS 中的命令起源于 FreeBSD。所有版本都支持-a追加而不是覆盖(尽管如上所述,当您使用进程替换通过命名管道进行写入时,这不会帮助您追加)。-a是直到并包括FreeBSD 2.2.5的唯一选项。该-q选项是在 FreeBSD 2.2.6添加的。该-k选项是在 FreeBSD 2.2.7添加的

FreeBSD 2.2.5 之前,该script命令不允许给出特定命令,而是始终运行用户的 shell,由SHELL环境变量给出,/bin/sh如果变量未设置,则作为后备。从 FreeBSD 2.2.6 开始,可以在命令行上给出一个特定的命令script,而不是一个 shell。

因此,更高版本的 FreeBSD,包括今天常见的那些,script在调用命令的方式上类似于较新的 macOS 系统,例如您的系统。同样,旧版本的 FreeBSD 类似于旧版本的 macOS(见下文)。

请注意,perl在任何最新版本中,它都不是 FreeBSD 基本系统的一部分,而且bash从来没有。两者都可以使用包(例如 with pkg install perl5 bash bash-completion)或端口轻松安装。/bin/shFreeBSD提供的系统不支持进程替换。

旧版本的 macOS,以及任何其他功能较少的系统 script

我在Mac OS X 10.4 Tiger中,其中测试script接受唯一-a选择。它不接受-q-k。它仅包括在其打字稿7 中终端中显示的击键,就像 GNU/Linux 系统上的 util-linux 版本一样。

至少在我可以script在每个版本的 macOS 中找到可靠的文档来源之前(据我所知,只有10.9 Mavericks 联机帮助页可以随时在线获得),我建议 macOS 用户运行man script以检查他们的script命令接受的语法,它的行为方式默认值,以及它支持的选项。您可能希望在像我这样的旧版 macOS 上使用这些命令:

script >(./dewtell >>clean.log)
brew args...
exit
Run Code Online (Sandbox Code Playgroud)

这也适用于script 不支持许多选项的任何其他操作系统,或支持其他选项但您不想使用它们的操作系统。这种script用于启动 shell,在 shell 中运行您需要记录的任何命令,然后退出 shell 的方法是传统方式。

假装你的命令是你的外壳的丑陋黑客

如果你真的必须script用来运行单个命令而不是你的 shell 的一个新实例,那么你有时可以使用一个丑陋的 hack:你可以欺骗它认为你想要运行的命令实际上是你的 shell 。但是,在执行此操作之前,您应该三思而后行,因为如果它本身实际上咨询了环境变量来检查您使用的实际 shell,那么就会出现令人不快的不幸行为。SHELL=your-command script outfileyour-commandSHELL

此外,这对于由多个单词组成的命令并不容易——也就是说,你要向其传递一个或多个参数的命令。如果您之前在同一行上写过,那将成功地将的值作为 的值传递到的环境中,但是整个字符串将用作命令的名称,而不仅仅是第一个单词,并且不会传递任何参数到命令,而不是传递的所有其他单词。SHELL='brew args...'scriptbrew args...scriptSHELL

你可以通过编写一个shell脚本,称为解决这个run-brew或者你想调用它,无论是运行brewargs...,然后传递,作为价值SHELL的环境变量。制作run-brewshell 脚本后,通过script命令运行它可能如下所示:

SHELL=run-brew script >(./dewtell >>clean.log)
Run Code Online (Sandbox Code Playgroud)

由于上述原因,我建议不要使用将命令名称分配给 的方法SHELL,除非您正在执行的操作不重要或您确定它不会涉及使用SHELL. 由于 Homebrew 执行了许多非常复杂的操作,我建议不要实际运行这样的run-brew脚本。(将长而复杂的brew命令放在run-brew脚本中并没有错,只是使用SHELL=run-brewmakescript运行它。)

然而,当我用一个简单的程序代替 来测试上面显示的技术时,我确实发现这种方法有点有用。brew args...

测试和演示该技术

您可能会发现在比长brew命令更简单的命令上尝试其中一些方法很有用。我知道我做到了。

演示程序/测试输入生成器,以及使用的测试方法

我制作了这个简单的交互式 Perl 脚本,它写入标准错误,在标准输出上提示用户输入他们的名字,从标准输入中读取它,然后用彩色用户名向标准输出写入问候语:

#!/usr/bin/perl

use strict;
use warnings;
use Term::ANSIColor;

print STDERR $0, ": warning: this program is boring\n";
print "What's your name?  ";
chomp(my $name = <STDIN>);
printf "Hello, %s!\n", colored($name, 'cyan');
Run Code Online (Sandbox Code Playgroud)

我调用它colorhi并将它放在与dewtell 的脚本相同的目录中,我称之为dewtell.

在我自己的测试中,我在两个脚本中都替换#!/usr/bin/perl#!/usr/bin/env perl8我使用系统提供的 perl 5.22.1 和 5.6.2 和 5.8.9 版本在 Ubuntu 16.04 LTS 中进行了测试perlbrew;FreeBSD 11.1-RELEASE-p3 与提供的pkgperl 5.24.3 和版本 5.6.2、5.8.9 和 5.27.5 由perlbrew; 和 Mac OS X 10.4.11 Tiger,带有系统提供的 perl 5.8.6 和由perlbrew.

我重复测试与每个那些perl的版本的以下描述,第一测试系统提供的9版,然后使用perlbrew use以暂时使每一perlbrew-providedperl二进制首先在出现$PATH(例如,以测试的Perl 5.6.2,我跑perlbrew use 5.6.2,则下面显示了我正在测试的系统的命令)。

一位朋友在 macOS 10.11.6 El Capitan 中测试,使用原始 hashbang 行,导致使用系统提供的 perl 5.18.2,并没有测试任何其他解释器。该测试使用了我在 FreeBSD 上测试时运行的相同命令。

所有这些测试都成功了,除了 Mac OS X 10.4.11 Tiger 中系统提供的 perl失败,因为似乎是一个涉及正则表达式中字符类的奇怪错误,正如我之前详细描述的,如下所示一个例子。

在 Ubuntu 上

在包含脚本的目录中,我在 Ubuntu 系统上运行这些命令以生成带有转义序列和我可能键入的任何退格字符的打字稿:

printf 'Whatever header you want...\n\n' >dirty.log
script dirty.log -aqc ./colorhi
Run Code Online (Sandbox Code Playgroud)

我输入了Eliah,然后表现得好像我对它有更好的想法,用退格键擦除它并输入Bob from accounting。然后我按下Enter并受到了彩色的欢迎。然后我运行这些命令来单独生成一个没有转义序列的打字稿,没有任何我的真实姓名的迹象,以完全相同的方式与之交互(包括打字和擦除Eliah):

printf 'Whatever header you want...\n\n' >clean.log
script >(./dewtell >>clean.log) -qc ./colorhi
Run Code Online (Sandbox Code Playgroud)

vim以象征性的方式显示控制字符,cat -v并提供加亮或彩色文本的优势。这是显示的缓冲区的view dirty.log样子,但控制字符的表示为斜体,因此它们在这里脱颖而出:

Whatever header you want...

Script started on Thu 09 Nov 2017 07:17:19 AM EST
./colorhi: warning: this program is boring^M
What's your name?  Eliah^H ^H^H ^H^H ^H^H ^H^H ^HBob from accounting^M
Hello, ^[[36mBob from accounting^[[0m!^M
Run Code Online (Sandbox Code Playgroud)

这就是缓冲区的样子view clean.log

Whatever header you want...

Script started on Thu 09 Nov 2017 07:18:31 AM EST
./colorhi: warning: this program is boring
What's your name?  Bob from accounting
Hello, Bob from accounting!
Run Code Online (Sandbox Code Playgroud)

测试的每个解释器的结果都相同,当然时间戳除外。

在 FreeBSD(和 macOS 10.11.6 El Capitan)上

我在 FreeBSD 上以与在 Ubuntu 上相同的方式进行了测试,除了我使用这些命令来生成dirty.log

printf 'Whatever header you want...\n\n' >dirty.log
script -aq dirty.log ./colorhi
Run Code Online (Sandbox Code Playgroud)

我使用这些命令来生成clean.log

printf 'Whatever header you want...\n\n' >clean.log
script -q >(./dewtell >>clean.log) ./colorhi
Run Code Online (Sandbox Code Playgroud)

这些是我的朋友在 macOS 10.11 上运行以测试它的相同命令,尽管发出的输入与我的Eliah/Bob from accounting输入略有不同,但仍然键入一个名称,用退格键擦除并替换为另一个名称。因此,除了名称和退格符的数量外,输出是相似的。

与在FreeBSD测试的Perl的实现的所有四个和在MacOS 10.11所述一个(系统提供的)的实施,既dirty.logclean.log显示预期的输出。将 FreeBSD 结果与 Ubuntu 结果进行比较,不同之处在于没有任何时间戳,因为-q. 在 中成功删除了所有转义序列和回车符clean.log,以及所有退格符和其擦除退格符指示的字符。

在 Mac OS X 10.4.11 Tiger

我在旧的 Tiger 系统上以与在 Ubuntu 和 FreeBSD 上相同的方式进行了测试,除了我使用这些命令来生成dirty.log

printf 'Whatever header you want...\n\n' >dirty.log
SHELL=colorhi script -a dirty.log
Run Code Online (Sandbox Code Playgroud)

我使用这些命令来生成clean.log

printf 'Whatever header you want...\n\n' >clean.log
SHELL=colorhi script >(./dewtell >>clean.log)
Run Code Online (Sandbox Code Playgroud)

由于该系统的script命令不支持-q,结果包括(a)Script started标题后附加的行和(b)换行符后跟Script done在每个打字稿末尾附加的行。这两行都包含时间戳。除此之外,结果与在 Ubuntu 和 FreeBSD 上的结果相同,只是系统提供的perl. 正如预期的那样,来自 的相关行dirty.log总是以这种方式出现vim

Hello, ^[[36mBob from accounting^[[0m!^M
Run Code Online (Sandbox Code Playgroud)

使用系统提供的 perl 5.8.6,这是 中的相应行clean.log,显示6m0m,本应删除,剩下的:

Hello, 6mBob from accounting0m!
Run Code Online (Sandbox Code Playgroud)

对于每个perlbrew安装的 perls,所有转义序列都被完全正确地删除,并且该行clean.log看起来像这样,就像我在 Ubuntu 和 FreeBSD 上运行的所有 Perl 解释器一样:

Hello, Bob from accounting!
Run Code Online (Sandbox Code Playgroud)

笔记

1 该手册适用于 Bash 2。许多 Bash 用户使用的是主要版本 4,并且更愿意阅读当前 Bash 手册中有关进程替换管道和其他主题的内容。当前版本的 macOS 附带 Bash 3。

2