web-dev-qa-db-ja.com

STDERRとSTDOUTを異なる色で印刷するようにシェルを構成できますか?

stderrstdoutとは異なる色で印刷されるように端末を設定したいと思います。多分赤。これにより、2つを区別しやすくなります。

これを.bashrcで構成する方法はありますか?そうでない場合、これは可能ですか?


:この質問は、stderrstdout-を求める another とマージされました- およびユーザー入力エコー種類の色で出力されます。回答はどちらかの質問に対処している可能性があります。

67
Naftuli Kay

これは stderrのみを画面に表示するが、stdoutとstderrの両方をファイルに書き込む のハードバージョンです。

端末で実行されているアプリケーションは、単一のチャネルを使用して通信します。アプリケーションには2つの出力ポート、stdoutとstderrがありますが、どちらも同じチャネルに接続されています。

それらの1つを別のチャネルに接続し、そのチャネルに色を追加し、2つのチャネルをマージできますが、これにより2つの問題が発生します。

  • マージされた出力は、リダイレクトがなかった場合とまったく同じ順序にならない場合があります。これは、チャネルの1つで追加された処理に(少し)時間がかかるため、カラーチャネルが遅延する可能性があるためです。バッファリングが行われると、障害は悪化します。
  • 端末は、色を変更するエスケープシーケンスを使用して、表示色を決定します。 ␛[31mは、「赤い前景に切り替える」ことを意味します。これは、stderrへの出力が表示されているのと同じようにstdout宛ての出力が到着すると、出力の色が間違っていることを意味します。 (さらに悪いことに、エスケープシーケンスの途中にチャネル切り替えがあると、ゴミが表示されます。)

原則として、2つのptyをリッスンして(つまり、一方のチャネルで入力を受け付けず、もう一方のチャネルで出力を処理している)2つのptyをリッスンし、適切な色変更命令を使用して即座に端末に出力するプログラムを作成できます。端末と対話するプログラムを実行する能力を失います。このメソッドの実装については知りません。

LD_PRELOADがロードされたライブラリでwriteシステムコールを呼び出すすべてのlibc関数をフックすることにより、プログラムに適切な色変更シーケンスを出力させることもできます。 。既存の実装については sickillの回答 を参照してください。または、既存の実装については StéphaneChazelasの回答 を参照してください。 straceを活用する混合アプローチ。

実際には、それが当てはまる場合は、stderrをstdoutにリダイレクトし、 colortailmultitail 、または colorgcc または colormakeなどの特殊用途のカラー化ツール

¹ 疑似端末。バッファリングのため、パイプは機能しませんでした。ソースがバッファに書き込む可能性があり、これにより、カラライザとの同期性が失われます。

チェックアウト stderred 。 _LD_PRELOAD_を使用してlibcwrite()呼び出しにフックし、端末に送信されるすべてのstderr出力を色分けします。 (デフォルトでは赤で表示されます。)

37
sickill

ユーザー入力のカラーリングは、半分の場合、ターミナルドライバー(ローカルエコーを使用)によって出力されるため困難です。その場合、そのターミナルで実行されているアプリケーションは、ユーザーがテキストを入力するタイミングを認識できず、それに応じて出力カラーを変更できません。 。 (カーネル内の)疑似ターミナルドライバーだけが知っています(xtermのようなターミナルエミュレーターがキーを押すといくつかの文字を送信し、ターミナルドライバーはエコー用にいくつかの文字を返すことがありますが、xtermはそれらがローカルエコーまたはアプリケーションが疑似端末のスレーブ側に出力したもの)。

そして、ターミナルドライバーにエコーしないように指示する別のモードがありますが、今回のアプリケーションは何かを出力します。アプリケーション(gdb、bashなどのreadlineを使用するアプリケーションなど)は、ユーザー入力をエコーバックする以外に出力するものと区別するのが難しいstdoutまたはstderrで送信する場合があります。

次に、アプリケーションのstdoutをstderrと区別するために、いくつかの方法があります。

それらの多くは、コマンドstdoutおよびstderrをパイプにリダイレクトし、アプリケーションがパイプを読み取って色を付けることを含みます。これには2つの問題があります。

  • Stdoutがターミナルではなく(代わりにパイプのように)なると、多くのアプリケーションはその動作を適応させて出力のバッファリングを開始する傾向があります。つまり、出力は大きなチャンクで表示されます。
  • 2つのパイプを処理するのが同じプロセスであっても、アプリケーションによってstdoutとstderrに書き込まれたテキストの順序が保持される保証はありません。これは、読み取りプロセスが認識できないためです(両方から読み取るものがある場合)。 「stdout」パイプまたは「stderr」パイプから読み取りを開始するかどうか。

別のアプローチは、アプリケーションを変更して、標準出力と標準入力に色を付けることです。多くの場合、それは不可能または現実的ではありません。

次に、トリック(動的にリンクされたアプリケーションの場合)は、ハイジャック($LD_PRELOADを使用して sickill's answer を使用)することで、アプリケーションによって呼び出された出力関数が何かを出力し、コードを設定して、それらがstderrまたはstdoutで何かを出力するためのものかどうかに基づく前景色。ただし、これは、Cライブラリおよびアプリケーションから直接呼び出されるwrite(2) syscallを実行するすべての可能な関数をハイジャックして、stdoutまたはstderr(printf、puts、perror .. 。)、それでも、その動作を変更する可能性があります。

別のアプローチとして、PTRACEトリックをstraceまたはgdbのように使用して、write(2)システムコールが呼び出されるたびにフックし、write(2)はファイル記述子1または2にあります。

しかし、それはかなり大きなことです。

私が今使っているトリックは、LD_PRELOADを使用してstrace自体をハイジャックして(システムコールの前に自分自身をフックするという汚い仕事をする)、出力カラーを変更するように指示することです。 fd 1または2でwrite(2)を検出しました.

straceソースコードを見ると、出力がすべてvfprintf関数を介して行われていることがわかります。必要なのは、その機能をハイジャックすることだけです。

LD_PRELOADラッパーは次のようになります。

#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>

int vfprintf(FILE *outf, const char *fmt, va_list ap)
{
  static int (*orig_vfprintf) (FILE*, const char *, va_list) = 0;
  static int c = 0;
  va_list ap_orig;
  va_copy(ap_orig, ap);
  if (!orig_vfprintf) {
    orig_vfprintf = (int (*) (FILE*, const char *, va_list))
      dlsym (RTLD_NEXT, "vfprintf");
  }

  if (strcmp(fmt, "%ld, ") == 0) {
    int fd = va_arg(ap, long);
    switch (fd) {
    case 2:
      write(2, "\e[31m", 5);
      c = 1;
      break;
    case 1:
      write(2, "\e[32m", 5);
      c = 1;
      break;
    }
  } else if (strcmp(fmt, ") ") == 0) {
    if (c) write(2, "\e[m", 3);
    c = 0;
  }
  return orig_vfprintf(outf, fmt, ap_orig);
}

次に、次のようにコンパイルします。

cc -Wall -fpic -shared -o wrap.so wrap.c -ldl

そしてそれを次のように使用します:

LD_PRELOAD=/path/to/wrap.so strace -qfo /dev/null -e write -s 0 env -u LD_PRELOAD some-cmd

some-cmdbashに置き換えると、bashプロンプトと入力内容が赤(stderr)で表示され、zshで黒に表示される(zsh dupsのため)新しいfdにstderrを実行して、プロンプトとエコーを表示します)。

予想外のアプリケーション(色を使用するアプリケーションなど)でも、驚くほどうまく機能するようです。

着色モードは、端末と見なされるstraceのstderrに出力されます。アプリケーションがstdoutまたはstderrをリダイレクトする場合、ハイジャックされたstraceは、ターミナルにカラーエスケープシーケンスを書き込み続けます。

このソリューションには制限があります。

  • straceに固有の問題:パフォーマンスの問題、その中でstracegdbなどの他のPTRACEコマンドを実行できない、またはsetuid/setgidの問題
  • 個々のプロセスのstdout/stderrのwritesに基づいて色分けされています。たとえば、sh -c 'echo error >&2'では、errorはそれをitsstdoutに出力するため、echoは緑色になります。 (shはshのstderrにリダイレクトされますが、straceが見るすべてはwrite(1, "error\n", 6)です)。そしてsh -c 'seq 1000000 | wc'では、seqitsstdoutに対してたくさんまたはwritesを行うので、ラッパーはたくさんの(見えない)エスケープシーケンスを端末に出力することになります。
15

一度に1つのコマンドに対してこれを行うMike Schiraldiの Hilite を参照してください。私自身の gush は、セッション全体でこれを実行しますが、他の多くの機能/特異性を必要としない場合もあります。

4
Colin Macleod

ここに私がしばらく前にやった概念の証明があります。

Zshでのみ機能します。

# make standard error red
rederr()
{
    while read -r line
    do
        setcolor $errorcolor
        echo "$line"
        setcolor normal
    done
}

errorcolor=red

errfifo=${TMPDIR:-/tmp}/errfifo.$$
mkfifo $errfifo
# to silence the line telling us what job number the background job is
exec 2>/dev/null
rederr <$errfifo&
errpid=$!
disown %+
exec 2>$errfifo

また、setcolorと呼ばれる関数があることも前提としています。

簡略版:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}
4
Mikel

serverfaultに関するいくつかの以前の議論。

grc と、それについて役立つ blog も参照してください。

1
anonfunc