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

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

stderr私は赤以外の色で印刷されるように端末を設定したいと思いますstdout。これにより、2つを簡単に区別できます。

で設定する方法はありますか.bashrc?そうでなければこれは可能ですか?


ノート:この問題はマージされました。その他必要stderrstdout ユーザー入力をエコーし​​ます。から出力予定3色。答えは2つの質問のうちの1つです。

答え1

確認するstderred。端末に送信されたすべての出力に色を割り当てるために通話を接続するためにLD_PRELOAD使用されます。 (デフォルトは赤です。)libcwrite()stderr

答え2

これはより難しいバージョンです。画面にstderrのみを表示し、stdoutとstderrをファイルに書き込みます。

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

チャンネルの1つを別のチャンネルに接続し、そのチャンネルに色を追加して2つのチャンネルをマージできますが、これは2つの問題を引き起こします。

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

原則として、2つのptys1を同時に受信し(つまり、別のチャンネルの出力を処理している間に1つのチャンネルの入力を受け付けず)、適切な色変更コマンドを使用してすぐに端末に出力するプログラムを書くことができます。端末と対話するプログラムを実行できなくなります。この方法の実装についてはわかりません。

write別の可能な方法は、ロードされたライブラリからシステムコールを呼び出すすべてのlibc関数をリンクして、プログラムが正しい色の変更順序を出力するようにすることです。LD_PRELOAD。バラよりシギルの答え既存の実装の場合、またはStefan Chazerasの答え混合方法の使用strace

実際には、該当する場合は、stderrをstdoutにリダイレクトし、パターンベースのシェーダにパイピングすることをお勧めします。あやおまたはマルチテールまたは、次の特殊目的シェーダカラーgccまたはカラー生産

擬似端末1個。バッファリングのため、パイプラインは機能しません。ソースはバッファに書き込むことができ、これはシェーダとの同期を中断します。

答え3

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

その後、端末ドライバにエコーしないように指示する別のモードがありますが、今回はアプリケーションが何かを出力します。ユーザー入力をエコーする代わりに、アプリケーション(gdb、bashなどのreadlineを使用するアプリケーション)はそれをstdoutまたはstderrに送信することができます。

次に、アプリケーションの標準出力と標準エラーを区別するためのいくつかの方法があります。

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

  • stdoutが端末(パイプなど)になっていない場合、多くのアプリケーションは出力バッファリングを開始するように動作を調整する傾向があります。つまり、出力は大きな塊として表示されます。
  • 両方のパイプを処理する同じプロセスであっても、アプリケーションがstdoutとstderrに書き込んだテキストの順序が維持されるという保証はありません。両方) アプリケーションが stdout と stderr で作成したテキストの順序を保持して読み込みを開始するかどうか。

別のアプローチは、標準出力と標準入力の両方に色を付けるようにアプリケーションを変更することです。これはしばしば不可能または非実用的です。

次に、(動的にリンクされたアプリケーションの場合)トリックは、次のものを$LD_PRELOAD使用してハイジャックすることができます。シギルの答え)アプリケーションが何かを出力するために呼び出し、stderrまたはstdoutに何かを出力するかどうかに応じて前景色を設定するコードを含む出力関数。しかし、これは、Cライブラリとアプリケーションから直接呼び出されるシステムコールを実行する他のライブラリwrite(2)で可能なすべての機能をハイジャックすると、最終的にstdoutまたはstderr(printf、puts、perror ....)に何かを書く可能性があることを意味します。 、これにより動作が変わる可能性があります。

strace別のアプローチは、システムコールが呼び出されるたびにPTRACEトリックを使用して、ファイル記述子がファイル記述子1または2にあるかどうかに応じて出力色を設定することです。gdbwrite(2)write(2)

しかし、これはかなり大きな問題です。

私が使った1つのトリックは、LD_PRELOADを使用して自分自身をハイジャックし(すべてのシステムコールの前に自分自身を接続する汚れた作業を実行します)、fd 1または2が検出されたかどうかに依存するstraceように指示することです。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 -qqf -a0 -s0 -o /dev/null \
  -e write -e status=successful -P "$(tty)" \
  env -u LD_PRELOAD some-cmd

some-cmdで置き換えると、bashbashプロンプトと入力した内容は赤(stderr)で表示され、zsh代替項目は黒で表示されます(zshがプロンプトとエコーを表示するためにstderrを新しいfdにコピーするため)。

予期しないアプリケーション(色を使用するアプリケーションなど)でも驚くほどうまく機能しているようです。

straceシェーディングモードは、端末とみなされたstderrに出力されます。を使用すると、-P "$(tty)"stdout / stderrがリダイレクトされている場合など、端末に送信されていない書き込みに対してこれを回避できます。

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

  • 本質的なもの:パフォーマンスの問題、またはstraceその中で他のPTRACEコマンドを実行できない、setuid / setgidの問題stracegdb
  • 色はwrite各個々のプロセスのstdout / stderrに基づいています。たとえば、sh -c 'echo error >&2'次のように出力されるのでerror緑になります。echoそれstdout(shはshのstderrにリダイレクトされますが、straceが見るすべてはaですwrite(1, "error\n", 6))。

2021年10月に修正されました。このラッパーは、strace 5.10、glibc 2.32、gcc 10.30.0を使用する不安定なDebianでは動作しなくなりました。ラップする必要がある関数__vfprintf_chkと検索する必要がある型が変更されたためです。ラッパーを次に変更する必要があります。

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

int __vfprintf_chk(FILE *outf, int x, const char *fmt, va_list ap)
{
  static int (*orig_vfprintf) (FILE*, int, 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*, int, const char *, va_list))
      dlsym (RTLD_NEXT, "__vfprintf_chk");
  }

  if (strcmp(fmt, "%d") == 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, "= %lu") == 0) {
    if (c) write(2, "\e[m", 3);
    c = 0;
  }
  return orig_vfprintf(outf, x, fmt, ap_orig);
}

このアプローチは、文書化されていない不安定なAPI(実際のAPIではない)を使用するため、わずかに脆弱であることを示しています。

答え4

これは私がしばらく前にやった概念証明です。

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
}

関連情報