แก้ไข: ฉันเห็นว่าฉันตกรางและจบลงด้วยการตอบคำถามที่แตกต่างจากที่ถาม คำตอบสำหรับคำถามที่แท้จริงอยู่ที่ด้านล่างของคำตอบของ Paul Tomblin (หากคุณต้องการปรับปรุงโซลูชันดังกล่าวเพื่อเปลี่ยนเส้นทาง stdout และ stderr แยกกันด้วยเหตุผลบางประการคุณสามารถใช้เทคนิคที่ฉันอธิบายไว้ที่นี่)
ฉันต้องการคำตอบที่รักษาความแตกต่างระหว่าง stdout และ stderr น่าเสียดายที่คำตอบทั้งหมดที่ได้รับจนถึงตอนนี้ที่รักษาความแตกต่างนั้นเป็นเรื่องที่เกิดจากการแข่งขันได้ง่าย: พวกเขาเสี่ยงที่โปรแกรมจะเห็นข้อมูลที่ป้อนไม่ครบถ้วนดังที่ฉันได้ชี้ให้เห็นในความคิดเห็น
ฉันคิดว่าในที่สุดฉันก็พบคำตอบที่รักษาความแตกต่างไม่ได้มีแนวโน้มที่จะแข่งขันและก็ไม่ได้เล่นซอด้วยเช่นกัน
Building Block แรก: เพื่อสลับ stdout และ stderr:
my_command 3>&1 1>&2 2>&3-
หน่วยการสร้างที่สอง: หากเราต้องการกรอง (เช่นที) เฉพาะ stderr เราสามารถทำได้โดยการสลับ stdout & stderr กรองแล้วสลับกลับ:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
ตอนนี้ส่วนที่เหลือเป็นเรื่องง่าย: เราสามารถเพิ่มตัวกรอง stdout ได้ในตอนเริ่มต้น:
{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-
หรือตอนท้าย:
{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter
เพื่อให้มั่นใจว่าทั้งสองคำสั่งข้างต้นใช้ได้ผลฉันใช้สิ่งต่อไปนี้:
alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'
ผลลัพธ์คือ:
...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr
และข้อความแจ้งของฉันจะกลับมาทันทีหลังจาก " teed stderr: to stderr
" ตามที่คาดไว้
เชิงอรรถเกี่ยวกับ zsh :
วิธีแก้ปัญหาข้างต้นใช้งานได้ใน bash (และอาจจะเป็นเชลล์อื่น ๆ ฉันไม่แน่ใจ) แต่มันใช้ไม่ได้ใน zsh มีสองสาเหตุที่ล้มเหลวใน zsh:
2>&3-
zsh ไม่เข้าใจไวยากรณ์ ที่จะต้องเขียนใหม่เป็น2>&3 3>&-
- ใน zsh (ไม่เหมือนเชลล์อื่น ๆ ) หากคุณเปลี่ยนเส้นทางตัวอธิบายไฟล์ที่เปิดอยู่แล้วในบางกรณี (ฉันไม่เข้าใจอย่างถ่องแท้ว่ามันตัดสินใจอย่างไร) มันจะมีพฤติกรรมเหมือนทีออฟในตัวแทน เพื่อหลีกเลี่ยงปัญหานี้คุณต้องปิดแต่ละ fd ก่อนที่จะเปลี่ยนเส้นทาง
ตัวอย่างเช่นวิธีแก้ปัญหาที่สองของฉันต้องเขียนใหม่สำหรับ zsh เป็น{my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter
(ซึ่งใช้งานได้ใน bash ด้วย แต่เป็นคำที่ละเอียดมาก)
ในทางกลับกันคุณสามารถใช้ประโยชน์จาก teeing โดยปริยายลึกลับของ zsh เพื่อหาวิธีแก้ปัญหาที่สั้นกว่ามากสำหรับ zsh ซึ่งไม่ได้วิ่งทีเลย:
my_command >&1 >stdout.txt 2>&2 2>stderr.txt
(ฉันไม่สามารถเดาได้จากเอกสารฉันพบว่า>&1
และ2>&2
เป็นสิ่งที่กระตุ้นการ teeing โดยปริยายของ zsh ฉันพบว่าเกิดจากการลองผิดลองถูก)