STDERR 및 STDOUT을 다른 색상으로 인쇄하도록 셸을 구성할 수 있나요?

STDERR 및 STDOUT을 다른 색상으로 인쇄하도록 셸을 구성할 수 있나요?

stderr나는 빨간색이 아닌 다른 색상으로 인쇄 되도록 터미널을 설정하고 싶습니다 stdout. 이렇게 하면 둘을 더 쉽게 구별할 수 있습니다.

에서 구성하는 방법이 있나요 .bashrc? 그렇지 않다면 이것이 가능합니까?


노트: 이 문제는 병합되었습니다.다른필요하다 stderr,stdout 사용자 입력을 에코합니다.에서 출력 예정3가지 색상. 대답은 두 질문 중 하나일 수 있습니다.

답변1

확인하다stderred. 터미널로 전송된 모든 출력에 색상을 지정하기 위해 호출을 LD_PRELOAD연결하는 데 사용됩니다 . (기본값은 빨간색입니다.)libcwrite()stderr

답변2

이건 더 어려운 버전이에요화면에 stderr만 표시하고 stdout 및 stderr을 파일에 씁니다..

터미널에서 실행되는 애플리케이션은 단일 채널을 사용하여 통신합니다. 애플리케이션에는 stdout 및 stderr라는 두 개의 출력 포트가 있지만 둘 다 동일한 채널에 연결되어 있습니다.

채널 중 하나를 다른 채널에 연결하고 해당 채널에 색상을 추가한 다음 두 채널을 병합할 수 있지만 이로 인해 두 가지 문제가 발생합니다.

  • 병합된 출력은 리디렉션이 없는 경우와 정확히 동일한 순서가 아닐 수 있습니다. 이는 채널 중 하나에 추가된 처리에 (약간) 시간이 걸리기 때문에 색상 채널이 지연될 수 있기 때문입니다. 버퍼링이 있으면 장애가 더욱 악화됩니다.
  • 터미널은 색상 변경 이스케이프 시퀀스를 사용하여 표시 색상을 결정합니다(예: ␛[31m"빨간색 전경으로 전환"을 의미). 즉, stderr에 대한 일부 출력이 표시될 때 stdout에 대한 일부 출력이 도착하면 출력의 색상이 잘못됩니다. (더 나쁜 것은 이스케이프 시퀀스 중간에 채널 스위치가 있으면 쓰레기가 표시된다는 것입니다.)

원칙적으로 두 ptys1을 동시에 수신하고(즉, 다른 채널의 출력을 처리하는 동안 한 채널의 입력을 받아들이지 않고) 적절한 색상 변경 명령을 사용하여 즉시 터미널에 출력하는 프로그램을 작성하는 것이 가능합니다. 터미널과 상호 작용하는 프로그램을 실행할 수 없게 됩니다. 이 방법의 구현에 대해서는 모르겠습니다.

write또 다른 가능한 방법은 로드된 라이브러리에서 시스템 호출을 호출하는 모든 libc 함수를 연결하여 프로그램이 올바른 색상 변경 순서를 출력하도록 만드는 것입니다.LD_PRELOAD. 바라보다시길의 답변기존 구현의 경우 또는Stefan Chazeras의 답변혼합 방법의 사용 strace.

실제로, 해당되는 경우 stderr를 stdout으로 리디렉션하고 패턴 기반 셰이더로 파이핑하는 것이 좋습니다.아야오또는다중 꼬리또는 다음과 같은 특수 목적 셰이더컬러 gcc또는컬러 생산.

의사 터미널 1 개. 버퍼링으로 인해 파이프라인이 작동하지 않습니다. 소스가 버퍼에 쓸 수 있으며 이로 인해 셰이더와의 동기화가 중단됩니다.

답변3

절반의 경우 터미널 드라이버(로컬 에코 포함)에 의해 출력되기 때문에 사용자 입력 색상을 지정하는 것은 어렵습니다. 따라서 이 경우 해당 터미널에서 실행 중인 애플리케이션은 텍스트를 입력하고 출력을 변경하려고 할 때 사용자가 어디에 있는지 알 수 없습니다. 그에 따라 색상을 지정합니다. (커널에 있는) 의사 터미널 드라이버만이 특정 키를 누를 때 일부 문자를 보내는 것을 알고 있으며(xterm과 같은 터미널 에뮬레이터) 터미널 드라이버는 에코를 위해 일부 문자를 다시 보낼 수 있지만 xterm은 로컬 에코인지 여부를 알 수 없습니다. 또는 의사 터미널 슬레이브에 대한 애플리케이션 출력).

그런 다음 터미널 드라이버에 에코를 하지 말라고 지시하는 또 다른 모드가 있지만 이번에는 애플리케이션이 무언가를 출력합니다. 사용자 입력을 에코하는 대신 애플리케이션(예: gdb, bash 등과 같은 readline을 사용하는 애플리케이션)은 이를 stdout 또는 stderr로 보낼 수 있으며, 이는 다른 항목에 대해 출력하는 것과 구별하기 어려울 수 있습니다.

그런 다음 응용 프로그램의 표준 출력과 표준 오류를 구별하기 위해 여러 가지 방법이 있습니다.

이들 중 다수에는 stdout 및 stderr 명령을 파이프로 리디렉션하고 이러한 파이프를 읽어 색상을 지정하는 응용 프로그램이 포함됩니다. 여기에는 두 가지 문제가 있습니다.

  • 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)

그러나 이것은 꽤 큰 문제입니다.

내가 방금 사용한 한 가지 트릭은 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
}

관련 정보