Bash

如何取消緩衝從互動式命令傳遞到以 tee 結尾的管道中的輸出?

  • November 10, 2017

我正在對互動式命令進行故障排除,並希望:

  • 看到列印到我的螢幕上的輸出,其原始顏色,無緩衝或行緩衝(而不是緩衝),因為該命令產生它
  • 使用類似tee將這個命令的輸出同時重定向到一個文件,保留——也就是說,不是亂碼(例如通過原始的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 mailutilsHomebrew 包管理器brew在哪裡,我試圖從原始碼建構並嘗試升級到的 Mailutils 版本是 v3.3(我目前有 v3.2),但我非常懷疑將其切換出去使用完全不同的命令,該命令在用作管道的一部分時也會產生塊緩衝輸出(如果我理解**正確,它將是任何shell 命令,正如我所看到的那樣,設置管道可能會導致塊緩衝和本身)不會在這裡解決我的問題。對於我試圖在此處擷取的日誌,我可以使用什麼工具或工具來實現我所尋求的結果?

流程替換1允許您script在編寫打字稿時對其進行轉換。

您可以使用而不是編寫刪除了轉義序列的日誌,其中任何命令執行刪除它們的腳本。在相對較新版本的 macOS 或 FreeBSD 以外的其他系統上,該命令會略有不同,因為不同的實現支持不同的選項。請參閱下面的完整詳細資訊。script -q >(./dewtell >>*outfile*) brew *args...*``script -aq *outfile* brew *args...*``./dewtell``script

迄今為止的故事(包括替代解決方案)

您的解決方案

您的命令將許多其他輸出生成程序作為子brew程序執行,例如./configure腳本make和實時。brew``tee``brew *args...* 2>&1 | tee -a *outfile*

您發現使用script,通過執行,通過將標準輸出保持為終端2script -aq來解決此問題,並且如果您希望自己的輸入被記錄*,即使在您鍵入時它沒有回顯到終端的情況下*,您也可以通過。您進一步發現dewtell 的 Gilles 的 Perl 腳本的擴展版本用於文件中刪除轉義序列可以有效地清理生成的打字稿,將其轉換為您需要的內容。-k

Gilles 的原始腳本和 dewtell 的擴展版本之間的區別在於,雖然它們都刪除了轉義序列,包括但不限於指定顏色更改的轉義序列,但dewtell的腳本還刪除了輸入符($'\r',表示為以及退格字元(,表示為和的輸出)以及它們似乎已刪除的任何字元(如果有)。^M``vim``cat -v``$'\b'``^H``vim``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不在 character class[@-~]中,即使使用LC_COLLATE=CorLC_ALL=C並且即使系統提供的grep, sed, python, 並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,執行where是由 生成的打字稿,是要用於備份文件的任何後綴,並且是 Perl 腳本。或者可以改為執行,因為沒有提供備份後綴,所以會丟棄原始文件。這些方法不適用於所有 Perl 腳本,但它們適用於這個腳本,因為它用於讀取輸入,並且在 Perl 方面perl -i.orig dewtell *typescript*typescriptscript``.orig``dewtell``perl -i dewtell *typescript*``<><>``-i

您曾經sponge將更改寫回原始打字稿。這也是一種很好且可靠的方法,儘管它需要安裝moreutils

防止轉義序列被記錄

剩下的問題是如何編寫一個首先刪除了轉義序列的日誌。正如你所說

$$ T $$這裡可能還有一種方法可以將所有這些都串聯到一個管道中,但是在撰寫本文時我還無法弄清楚;其他評論或答案顯示如何…

使用流程替換而不是管道。

問題是script在大多數(所有?)系統上都沒有支持這些轉換的選項。由於script寫入文件的名稱是您指定或預設為– 不是typescript標準輸出 – 管道script不會影響寫入打字稿的內容。

將命令放在管道運算符 ( )script的右側以通過管道傳送到它也不是一個好主意。在您的情況下,這特別是因為當其標準輸出是管道時,來自或其子程序的輸出被緩衝,因此當您需要查看它時它沒有出現。|``brew

即使解決了這個問題,我也不知道有什麼合理的方法可以使用管道1來script完成這項任務。

**但這可以通過程序替換來完成。**3在程序替換1中(也在這裡解釋),你寫or 。shell 創建一個命名管道並將其分別用作執行的子shell的標準輸出或輸入。文本or被命名管道的文件名替換——這就是替換——因此您可以將其作為參數傳遞給程序或將其用作重定向的目標。<(*command...*)``>(*command...*)command...<(*command...*)``>(*command...*)

  • 用於執行,就像它的輸出是您將從中讀取的文件的內容一樣。4<(*command...*)command...
  • 用於執行,就像它的輸入是您將寫入的文件的內容一樣。4>(*command...*)command...

並非所有系統都支持命名管道,但大多數都支持。並非所有的 shell 都支持程序替換,但 Bash 支持,只要它在能夠支持它的系統上執行,你的 Bash 建構並沒有省略對它的支持,並且在 shell 中關閉了 POSIX 模式。在 Bash 中,您通常可以訪問程序替換,尤其是在您使用任何遠端最近的作業系統時。即使在我的 Mac OS X 10.4.11 Tiger (PPC) 系統上"$BASH_VERSION"2.05b.0(1)-release程序替換也能正常工作。

script以下是在最近的 macOS 系統上使用 ’s 語法時如何做到這一點。

這應該適用於您的 macOS 10.11 El Capitan 系統——並且,根據該手冊頁,任何 macOS 系統至少可以追溯到 macOS 10.9 Mavericks甚至更早:

script -q >(./dewtell >>*clean.log*) brew *args...*

這會記錄寫入終端的所有內容,包括您自己的輸入*(如果它回顯給您*),即,如果它出現在終端中,它通常會這樣做。如果您希望自己的輸入即使沒有出現也被記錄下來,請記住,發生這種情況的情況通常是您正在輸入密碼,那麼正如您在回答中提到的那樣,添加-k選項:

script -kq >(./dewtell >>*clean.log*) brew *args...*

在任何一種情況下,用./dewtell執行dewtell 腳本的任何命令或任何其他程序或腳本替換你想用來過濾輸出的任何其他程序或腳本,*clean.log*用你想寫打字稿的文件的名稱省略轉義序列,用你的命令替換5執行及其參數。brew *args...*

覆蓋或附加到日誌

如果您想覆蓋*clean.log*而不是附加到它,請使用而不是. 實際文件是由通過程序替換執行的命令寫入的,因此or重定向運算符出現在.**>***clean.log*``**>>***clean.log*``>``>>``>( )

不要嘗試使用>>(而不是>(,這是一個語法錯誤並且沒有意義,因為>in >(for 程序替換並不意味著重定向。

不要傳遞-ascript它會阻止您的日誌文件在這種情況下被覆蓋的意圖,因為這只會以附加模式打開命名管道- 這與打開它以進行正常寫入具有相同的效果 - 並且然後覆蓋或附加*clean.log*,仍然取決於子shell中是否使用或。**>***clean.log*``**>>***clean.log*

同樣,不要在內部(或任何地方)使用>&&>或添加,因為如果生成任何錯誤或警告,您會希望看到這些而不是將它們寫入. 該命令自動在其打字稿中包含來自標準錯誤的文本;你不需要做任何特別的事情來實現這一點。2>&1``>( )``./dewtellclean.logscript

在其他作業系統上

正如你的回答所說:

$$ S $$該script命令的某些版本具有不同的語法;給定的是 OS X/macOS,所以根據需要進行調整。

GNU/Linux

大多數 GNU/Linux 系統使用util-linuxscript提供的實現。如果您想讓它執行特定命令而不是啟動 shell,則必須使用該選項並將整個命令作為單個命令行參數傳遞給,您可以通過將其括在引號中來實現。這與最近的 macOS 系統(如您的系統)上的版本不同,它允許您將命令自然地作為放置在輸出文件名之後的多個參數傳遞(沒有類似 的選項)。-c``script``script``-c

所以在 Debian、Ubuntu、Fedora、CentOS 和大多數其他 GNU/Linux 系統上,你可以使用這個命令(如果它有一個brew命令6,或者用你想要執行的任何命令替換它並記錄轉換後的輸出):

script >(./dewtell >>*clean.log*) -qc 'brew *args...*'

script在您的系統上一樣,在 GNU/Linux 上,-q如果您想script包含更多有關日誌記錄如何開始和結束的消息,請刪除。即使有這個-q選項,這個版本script的頂部仍然包含一行,說明它何時開始執行,儘管它沒有顯示該行,沒有寫或顯示任何關於它何時停止執行的內容。

沒有-k選擇。僅記錄出現在終端中的文本。7

自由BSD

macOS 中的script命令起源於 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)或埠輕鬆安裝。FreeBSD 中提供的系統/bin/sh不支持程序替換。

舊版本的 macOS,以及其他功能較少的系統script

我在僅script接受該選項的 Mac OS X 10.4 Tiger 上進行了測試。它不接受或。它僅包括終端中顯示在其 typescript 7中的擊鍵,與 GNU/Linux 系統上的 util-linux 版本一樣。-a``-q``-k

至少在我找到script每個 macOS 版本的可靠文件來源之前(據我所知,只有10.9 Mavericks 聯機幫助頁可以線上獲得),**我建議 macOS 使用者執行man script**以檢查他們的script命令接受的語法,它的行為方式預設值,以及它支持的選項。您可能想在像我這樣的舊版 macOS 上使用這些命令:

script >(./dewtell >>*clean.log*)
brew *args...*
exit

這也適用於script 不支持許多選項的任何其他作業系統,或支持其他選項但您不想使用它們的作業系統。這種script用於啟動 shell、在 shell 中執行您需要記錄的任何命令或命令,然後退出 shell 的方法是傳統方式。

假裝你的命令是你的外殼的醜陋黑客

如果你真的必須用它script來執行一個命令而不是你的 shell 的一個新實例,那麼你有時可以使用一個醜陋的 hack:你可以欺騙它,讓它認為你要執行的命令實際上是你的 shell 。但是,在執行此操作之前您應該三思而後行,因為如果它自己實際上諮詢了環境變數來檢查您使用的實際 shell,那麼就會出現歡鬧不幸的行為。SHELL=*your-command* script *outfile*your-commandSHELL

此外,這不適用於由多個單片語成的命令 - 即您正在向其傳遞一個或多個參數的命令。如果您之前在同一行上寫過,那將成功地作為 的值傳遞到’s 環境中,但是整個字元串將用作命令的名稱,而不僅僅是第一個單詞,並且不會傳遞任何參數到命令,而不是傳遞所有其他單詞。SHELL='brew *args...*'``script``brew *args...*``script``SHELL

你可以通過編寫一個 shell 腳本來解決這個問題,run-brew呼叫它或任何你想呼叫它,執行brew,*args...*然後將它作為SHELL環境變數的值傳遞。製作完run-brewshell 腳本後,通過script命令執行它可能如下所示:

SHELL=run-brew script >(./dewtell >>*clean.log*)

由於上述原因,我建議不要使用將命令名稱分配給 的方法SHELL,除非您正在執行的操作不重要或者您確定它不會涉及使用SHELL. 由於 Homebrew 執行許多非常複雜的操作,我建議不要實際執行這樣的run-brew腳本。brew(將長而復雜的命令放在腳本中並沒有錯run-brew,只需要使用SHELL=run-brewscript執行它。)

但是,在用一個簡單的程序代替 測試上面顯示的技術時,我確實發現這種方法有點有用。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');

我呼叫它colorhi並將它放在與我呼叫的 dewtell 腳本dewtell相同的目錄中。

在我自己的測試中,我在兩個腳本中都替換#!/usr/bin/perl為。8我在 Ubuntu 16.04 LTS 中使用系統提供的 perl 5.22.1 以及 5.6.2 和 5.8.9 提供的版本進行了測試;FreeBSD 11.1-RELEASE-p3 與-provided perl 5.24.3 和版本 5.6.2、5.8.9 和 5.27.5 提供;和 Mac OS X 10.4.11 Tiger 以及系統提供的 perl 5.8.6 和版本 5.6.2、5.8.9 和 5.27.5 由.#!/usr/bin/env perl``perlbrew``pkg``perlbrew``perlbrew

我對每個 perl 版本重複了下面描述的測試,首先測試系統提供的9版本,然後使用perlbrew use臨時導致每個提供的perlbrew二進製perl文件首先出現$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

我輸入Eliah了 ,然後表現得好像我想得更好,用退格鍵擦除它並Bob from accounting改為輸入。然後我按下Enter並受到顏色的歡迎。然後我執行這些命令分別生成一個打字稿,沒有轉義序列,也沒有任何我的真實姓名的跡象,以完全相同的方式與之互動(包括打字和擦除Eliah):

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

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 ^H*Bob from accounting*^M*
Hello, *^[*[36mBob from accounting*^[*[0m!*^M*

這就是緩衝區的樣子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!

測試的每個解釋器的結果都相同,當然時間戳除外。

在 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

我使用這些命令來生成clean.log

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

這些是我的朋友在 macOS 10.11 上執行以測試它的相同命令Eliah,儘管發出的輸入與我的/輸入略有不同,Bob from accounting但仍然輸入了一個名稱,用退格鍵擦除,並替換為另一個名稱。因此,除了退格的名稱和數量之外,輸出是相似的。

在 FreeBSD 上測試了所有四個 Perl 實現,在 macOS 10.11 上測試了一個(系統提供的)實現,兩者都dirty.log顯示clean.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

我使用這些命令來生成clean.log

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

由於該系統的script命令不支持-q,因此結果包括*(a)Script started在標題之後附加的一行和(b)*一個換行符,然後Script done在每個打字稿的最後附加一行。這兩行都包含時間戳。除此之外,結果與在 Ubuntu 和 FreeBSD 上的結果相同,只是在系統提供的perl. 正如預期的那樣,來自的相關行dirty.log總是以這種方式出現在 中vim

Hello, *^[*[36mBob from accounting*^[*[0m!*^M*

在系統提供的 perl 5.8.6 中,這是 , 中的對應行clean.log, 顯示6m0m, 應該被刪除, 留下來:

Hello, 6mBob from accounting0m!

對於每個安裝的perlbrewperls,所有轉義序列都被完全正確地刪除,並且該行clean.log看起來像這樣,就像我在 Ubuntu 和 FreeBSD 上執行的所有 Perl 解釋器一樣:

Hello, Bob from accounting!

筆記

1 該手冊適用於 Bash 2。許多 Bash 使用者都在使用主要版本 4,並且更願意閱讀目前 Bash 手冊中有關程序替換管道和其他主題的內容。目前版本的 macOS 附帶 Bash 3。

2 標準錯誤幾乎總是無緩衝的,無論它是什麼類型的文件或設備。沒有規定程序不能緩衝對文件描述符 2 的寫入,但有一個強大的傳統是不這樣做,因為需要在錯誤和警告消息發生時實際看到它們 - 並且還需要看到它們,即使程序在沒有正確關閉或以其他方式刷新其打開的文件描述符的情況下異常終止。預設情況下,程序將寫入緩衝到標準錯誤通常是一個錯誤。

3程序替換使用命名管道,也稱為FIFO,它實現了與 shell 中的管道運算符相同的一般目標|,但更通用。然而,即使這是一個 pipe,我認為它不是一個 pipeline,我認為它是指一個 shell 的特定語法結構和相應的行為。

4如果您認為命名管道是一個文件,您應該這樣做,那麼這就是正在發生的事情。

5儘管"$COMMAND"出現在您的答案中並將整個命令作為單個參數傳遞給script(因為雙引號抑制分詞),但您能夠將命令script 作為多個參數傳遞給。

6比如Linuxbrew,我應該承認是你介紹給我的

7但是,我建議任何依賴這種行為來保密敏感數據的人測試他們的script命令的行為,甚至可能檢查生成的打字稿以確保不存在必須保護的數據。為了更加安全,請使用顯示通常隱藏在螢幕上的字元的編輯器,或使用cat -v.

8帶有 的版本#!/usr/bin/perl,包括colorhi所示的實現,應該可以在大多數係統上工作並做正確的事情。我#!/usr/bin/env perl在自己的測試中使用過。但是我的朋友使用了與您(原始海報)執行相同的作業系統#!/usr/bin/perl。這實現了以最小的複雜性或懷疑的可能性檢查系統提供的 perl 是否可以工作的目標。

9在 FreeBSD 上,沒有嚴格意義上的系統提供的 perl。我pkg首先測試了通過安裝的版本。

引用自:https://unix.stackexchange.com/questions/401956