ฉันสามารถกำหนดค่าเปลือกของฉันเพื่อพิมพ์ STDERR และ STDOUT ในสีที่ต่างกันได้หรือไม่?


62

ฉันต้องการตั้งค่าเทอร์มินัลของฉันเพื่อให้stderrพิมพ์ในสีที่แตกต่างกว่าstdout; อาจจะเป็นสีแดง นี่จะทำให้ง่ายต่อการบอกความแตกต่าง

มีวิธีกำหนดค่านี้.bashrcไหม ถ้าไม่เป็นไปได้ไหม


หมายเหตุ : คำถามนี้รวมกับอีกว่าขอให้stderr, stdout และผู้ใช้ป้อนก้องจะเป็นผลลัพธ์ใน3 สีที่แตกต่างกัน คำตอบอาจตอบคำถามใดคำถามหนึ่ง


1
คำถามเดียวกันเกี่ยวกับ Stack Overflow: stackoverflow.com/questions/6841143/…
Stéphane Gimenez

คำถามที่น่าสนใจ + คำตอบ แต่สีแดงยืนออกมากเกินไป IMO ตั้งแต่stderr ไม่ได้เป็นเพียงความผิดพลาด
krookedking

คำตอบ:


32

นี้เป็นรุ่นที่ยากของการแสดงเฉพาะ stderr บนหน้าจอ แต่เขียนทั้ง stdout และ stderr ที่จะยื่น

แอปพลิเคชั่นที่ทำงานในเครื่องเทอร์มินัลใช้ช่องทางเดียวในการสื่อสารกับมัน แอปพลิเคชั่นมีพอร์ตเอาต์พุตสองพอร์ตคือ stdout และ stderr แต่ทั้งคู่เชื่อมต่อกับช่องสัญญาณเดียวกัน

คุณสามารถเชื่อมต่อช่องใดช่องหนึ่งเข้ากับช่องสัญญาณต่าง ๆ เพิ่มสีลงในช่องนั้นและผสานทั้งสองช่อง แต่จะทำให้เกิดปัญหาสองประการ:

  • ผลลัพธ์ที่ถูกผสานอาจไม่เหมือนกันในลำดับเดียวกับที่ไม่มีการเปลี่ยนเส้นทาง นี่เป็นเพราะการประมวลผลที่เพิ่มเข้ามาในช่องสัญญาณหนึ่งใช้เวลา (น้อย) ดังนั้นช่องสีอาจล่าช้า หากบัฟเฟอร์ใด ๆ เสร็จสิ้นความผิดปกติจะเลวร้ายยิ่ง
  • เทอร์มินัลใช้ลำดับการเปลี่ยนสีเพื่อกำหนดสีของจอแสดงผลเช่น␛[31mหมายถึง“ เปลี่ยนเป็นสีแดงเบื้องหน้า” ซึ่งหมายความว่าหากเอาต์พุตบางตัวที่กำหนดให้ stdout มาถึงเช่นเดียวกับเอาต์พุตบางส่วนสำหรับ stderr ที่ถูกแสดงเอาต์พุตจะถูกผิดพลาด (ยิ่งแย่กว่านั้นถ้ามีช่องสลับไปมาระหว่างลำดับการหลบหนีคุณจะเห็นขยะ)

โดยหลักการแล้วมันเป็นไปได้ที่จะเขียนโปรแกรมที่ฟังสองptys¹แบบซิงโครนัส (นั่นคือจะไม่รับอินพุตในช่องหนึ่งในขณะที่กำลังประมวลผลเอาต์พุตบนช่องอื่น) และส่งสัญญาณไปยังเทอร์มินัลทันที คุณสูญเสียความสามารถในการเรียกใช้โปรแกรมที่ทำงานกับเครื่องเทอร์มินัล ฉันไม่รู้ว่าจะใช้วิธีนี้อย่างไร

อีกวิธีที่เป็นไปได้ที่จะทำให้โปรแกรมการส่งออกสีที่เหมาะสมการเปลี่ยนแปลงลำดับโดย hooking ทั่วทุกฟังก์ชั่ libc ที่โทรสายระบบในห้องสมุดที่เต็มไปด้วยwrite LD_PRELOADดูคำตอบ sickill ของการดำเนินงานที่มีอยู่หรือคำตอบStéphane Chazelas ของstraceสำหรับวิธีการผสมที่ใช้ประโยชน์จาก

ในทางปฏิบัติหากที่บังคับผมขอแนะนำให้เปลี่ยนเส้นทาง stderr ที่ stdout และท่อเป็น Colorizer รูปแบบที่ใช้เช่นcolortailหรือmultitailหรือ colorizers เช่นวัตถุประสงค์พิเศษcolorgccหรือcolormake

¹ หลอกขั้ว ท่อไม่ทำงานเนื่องจากการบัฟเฟอร์: แหล่งที่มาสามารถเขียนไปยังบัฟเฟอร์ซึ่งจะทำลายความบังเอิญด้วย colorizer


1
อาจไม่ยากที่จะแก้ไขโปรแกรมเทอร์มินัลเพื่อสตรีม stderr ใครบางคนได้แนะนำอะไรเช่นนี้ที่อูบุนตูระดมความคิด
intuited

@intuited: ที่จะต้องทำการสร้างเทอร์มินัลอีมูเลเตอร์ทุกตัวที่คุณต้องการให้มันทำงานด้วย การใช้LD_PRELOADเคล็ดลับในการสกัดกั้นการwriteโทรน่าจะเหมาะสมที่สุด IMO (แต่หลังจากนั้นอีกครั้งอาจมีความแตกต่างของรสชาติ * แน่นอน)
อเล็กซ์

อย่างน้อยบน Linux การสกัดกั้นwriteเพียงอย่างเดียวจะไม่ทำงานเนื่องจากแอปพลิเคชันส่วนใหญ่ไม่ได้โทรโดยตรง แต่ฟังก์ชั่นอื่นจากห้องสมุดสาธารณะบางแห่ง (เช่นprintf) ที่เรียกว่าต้นฉบับwrite
Stéphane Chazelas

@ StephanChazelas ฉันกำลังคิดที่จะไปรอบ ๆwriteเสื้อคลุม syscall มัน inlined ในหน้าที่อื่น ๆ ใน Glibc?
Gilles

1
โครงการ stderredดูเหมือนว่าจะมีการดำเนินการของ hooking writeผ่านLD_PRELOADที่คุณอธิบาย
Drew Noakes

36

stderredตรวจสอบ โดยจะใช้LD_PRELOADในการขอการlibcของwrite()สายการระบายสีทั้งหมดstderrการส่งออกไปยังสถานี (สีแดงเป็นค่าเริ่มต้น)


8
นีซ, ห้องสมุดที่เป็นที่น่ากลัว คำถามที่แท้จริงคือ: ทำไมระบบปฏิบัติการ / เทอร์มินัลของฉันไม่มาพร้อมกับการติดตั้งนี้ล่วงหน้า ;)
Naftuli Kay

5
ฉันคิดว่าคุณเป็นผู้แต่งใช่มั้ย คุณควรเปิดเผยความร่วมมือของคุณในกรณีนั้น
Dmitry Grigoryev

15

การป้อนข้อมูลผู้ใช้ระบายสีทำได้ยากเพราะในครึ่งกรณีจะมีเอาต์พุตจากไดรเวอร์เทอร์มินัล (พร้อมเสียงสะท้อนในเครื่อง) ดังนั้นในกรณีนั้นไม่มีแอปพลิเคชันที่ทำงานอยู่ในเทอร์มินัลนั้นอาจทราบเมื่อผู้ใช้พิมพ์ข้อความและเปลี่ยนสีเอาต์พุต . เฉพาะไดรเวอร์เทอร์มินัลหลอก (ในเคอร์เนล) เท่านั้นที่รู้ (เทอร์มินัลอีมูเลเตอร์ (เช่น xterm) ส่งอักขระบางตัวเมื่อกดปุ่มและไดรเวอร์เทอร์มินัลอาจส่งอักขระบางตัวสำหรับ echo แต่ xterm ไม่สามารถรู้ได้ว่า echo โลคัลหรือจากแอ็พพลิเคชันที่ส่งออกไปยังฝั่งทาสของเทอร์มินัลหลอก)

และจากนั้นก็มีโหมดอื่น ๆ ที่ไดรเวอร์ของเทอร์มินัลบอกว่าไม่ให้สะท้อนเสียง แต่แอพพลิเคชั่นในครั้งนี้ให้ผลลัพธ์บางอย่าง แอปพลิเคชัน (เช่นผู้ที่ใช้ readline เช่น gdb, bash ... ) อาจส่งไปที่ stdout หรือ stderr ซึ่งจะยากที่จะแยกความแตกต่างจากสิ่งที่มันเอาท์พุทเพื่อสิ่งอื่นที่ไม่ใช่การสะท้อนกลับอินพุตของผู้ใช้

จากนั้นเพื่อแยกความแตกต่าง stdout ของแอปพลิเคชันจาก stderr ของมันมีหลายวิธี

หลายคนเกี่ยวข้องกับการเปลี่ยนเส้นทางคำสั่ง stdout และ stderr ไปป์และไปป์เหล่านั้นอ่านโดยแอปพลิเคชันเพื่อสี มีสองปัญหาคือ:

  • เมื่อ stdout ไม่ใช่เทอร์มินัลอีกต่อไป (เช่นไปป์แทน) แอปพลิเคชันจำนวนมากมักจะปรับพฤติกรรมของพวกเขาเพื่อเริ่มบัฟเฟอร์เอาต์พุตของพวกเขาซึ่งหมายความว่าเอาต์พุตจะถูกแสดงในกลุ่มก้อนขนาดใหญ่
  • แม้ว่าจะเป็นกระบวนการเดียวกับที่ประมวลผลสองไพพ์ แต่ก็ไม่มีการรับประกันว่าลำดับที่ข้อความที่เขียนโดยแอปพลิเคชันบน stdout และ stderr จะถูกเก็บรักษาไว้เนื่องจากกระบวนการอ่านไม่สามารถรู้ได้ (ถ้ามีบางอย่างที่ต้องอ่านจากทั้งคู่) ไม่ว่าจะเริ่มอ่านจากไปป์ "stdout" หรือไปป์ "stderr"

อีกวิธีหนึ่งคือการปรับเปลี่ยนแอปพลิเคชันเพื่อให้สีของ stdout และ stdin มันมักจะเป็นไปไม่ได้หรือเป็นจริงที่จะทำ

จากนั้นเคล็ดลับ (สำหรับแอปพลิเคชันที่เชื่อมโยงแบบไดนามิก) สามารถขโมยได้ (ใช้$LD_PRELOADในคำตอบของ sickill ) ฟังก์ชั่นการส่งออกที่เรียกใช้โดยแอปพลิเคชันเพื่อส่งออกบางสิ่งบางอย่างและใส่รหัสในรหัสที่กำหนดสีพื้นหน้า บน stderr หรือ stdout อย่างไรก็ตามนั่นหมายถึงการหักหลังทุกฟังก์ชั่นที่เป็นไปได้จาก C library และwrite(2)ไลบรารี่อื่น ๆ ที่เรียก syscall โดยตรงโดยแอพพลิเคชั่นที่อาจจะเขียนอะไรลงบน stdout หรือ stderr (printf, put, perror ... ) และแม้แต่ตอนนั้น ที่อาจปรับเปลี่ยนพฤติกรรมของมัน

อีกวิธีหนึ่งที่อาจจะใช้เทคนิค ptrace เป็นstraceหรือgdbทำเพื่อตัวเราเองขอเวลาทุกwrite(2)สายระบบที่เรียกว่าและการตั้งค่าสีที่ส่งออกขึ้นอยู่กับว่าwrite(2)อยู่ในไฟล์อธิบาย 1 หรือ 2

อย่างไรก็ตามนั่นเป็นเรื่องใหญ่ที่ต้องทำ

เคล็ดลับที่ฉันเพิ่งเล่นด้วยคือการจี้straceตัวเอง (ซึ่งเป็นการทำงานที่สกปรกของ hooking ตัวเองก่อนการเรียกระบบทุกครั้ง) โดยใช้ LD_PRELOAD เพื่อบอกให้เปลี่ยนสีเอาต์พุตตามว่ามันตรวจพบ a write(2)fd 1 หรือ 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-cmdด้วยbashbash prompt และสิ่งที่คุณพิมพ์จะปรากฏเป็นสีแดง (stderr) ในขณะที่zshมันปรากฏเป็นสีดำ (เพราะ zsh dups stderr ไปยัง fd ใหม่เพื่อแสดงพร้อมท์และ echo)

ดูเหมือนว่าจะใช้งานได้ดีอย่างน่าประหลาดใจแม้กับแอปพลิเคชันที่คุณคาดหวังไม่ได้ (เช่นเดียวกับที่ใช้สี)

โหมดการระบายสีจะถูกแสดงผลบนstracestderr ของซึ่งจะถือว่าเป็นขั้ว หากแอปพลิเคชั่นเปลี่ยนเส้นทาง stdout หรือ stderr ของเราที่ถูกแย่งชิง strace ของเราจะทำการเขียนลำดับการหลีกเลี่ยงสีบนเทอร์มินัล

โซลูชันนั้นมีข้อ จำกัด :

  • สิ่งเหล่านี้มีอยู่ในstrace: ปัญหาด้านประสิทธิภาพคุณไม่สามารถเรียกใช้คำสั่ง PTRACE อื่น ๆ เช่นstraceหรือgdbในนั้นหรือปัญหา setuid / setgid
  • มันเป็นสีตามwrites บน stdout / stderr ของแต่ละกระบวนการ ดังนั้นสำหรับตัวอย่างเช่นในsh -c 'echo error >&2', errorจะเป็นสีเขียวเพราะechoผลบนของ stdout (ซึ่งดวลจุดโทษเปลี่ยนเส้นทางไปยัง stderr ดวลจุดโทษ แต่ strace ทั้งหมดเห็นเป็นwrite(1, "error\n", 6)) และในsh -c 'seq 1000000 | wc'นั้นseqทำอะไรมากมายหรือwrites ไปยังstdout ของมันดังนั้นเสื้อคลุมจะจบลงด้วยการส่งลำดับ escape (ที่มองไม่เห็น) จำนวนมากไปยังเทอร์มินัล

ดี มีข้อเสนอแนะของห่อมาก่อนอยู่บนคำถามที่ซ้ำกัน ฉันตั้งค่าสถานะคำถามเพื่อรวมเข้าด้วยกันเพื่อให้สามารถเห็นคำตอบของคุณได้
Gilles

อาจจะปรับแต่งไฮไลต์ไวยากรณ์เป็นกลุ่ม? strace $CMD | vim -c ':set syntax=strace' -.
ปาโบล

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
}

exec 2> >(rederr)มีวิธีที่ง่ายมากในการทำเช่นนี้คือ: ทั้งสองเวอร์ชันจะมีปัญหาที่ฉันพูดถึงในคำตอบของฉันของการเรียงลำดับบรรทัดใหม่และการเสี่ยงเอาต์พุตแบบ mangled (โดยเฉพาะกับบรรทัดที่ยาว)
Gilles

ฉันลองแล้วมันใช้งานไม่ได้
Mikel

seterrจะต้องเป็นสคริปต์แบบสแตนด์อโลนไม่ใช่ฟังก์ชัน
Gilles

4

ดู Mike Schiraldi Hiliteซึ่งทำสิ่งนี้ได้ครั้งละคำสั่ง ความพรั่งพรูของฉันทำเช่นนี้ตลอดทั้งเซสชัน แต่ก็มีคุณสมบัติอื่น ๆ อีกมากมายที่คุณอาจไม่ต้องการ


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.