แสดงเฉพาะ stderr บนหน้าจอ แต่เขียนทั้ง stdout และ stderr ไปยังไฟล์


24

ฉันจะใช้ BASH Magic เพื่อให้ได้สิ่งนี้ได้อย่างไร
ฉันต้องการเห็นเฉพาะ stderr output บนหน้าจอ
แต่ฉันต้องการให้ stdout และ stderr เขียนลงไฟล์

ชี้แจง: ฉันต้องการให้ทั้ง stdout และ stderr สิ้นสุดในไฟล์เดียวกัน ตามลำดับที่เกิดขึ้น
น่าเสียดายที่ไม่มีคำตอบด้านล่างทำสิ่งนี้


คำตอบ:


14

แม้ไม่มีการเปลี่ยนเส้นทางหรือไม่มีอะไรเลย>logfile 2>&1คุณไม่รับประกันว่าจะเห็นผลลัพธ์ตามลำดับรุ่น

สำหรับ starters stdout จากแอ็พพลิเคชันจะเป็นแบบ line-buffered (ไปยัง tty) หรือ buffered (ไปยังไปป์ไลน์) แต่ stderr นั้นไม่มีบัฟเฟอร์ดังนั้นความสัมพันธ์ระหว่างคำสั่งของเอาต์พุตจะแตกเท่าที่ผู้อ่านเกี่ยวข้อง ขั้นตอนต่อมาในขั้นตอนการผลิตใด ๆ ที่คุณสามารถปรุงได้จะไม่ได้รับคำสั่งให้เข้าถึงสองสตรีมอย่างแน่นอน (มันเป็นสิ่งที่เกิดขึ้นในแนวความคิดขนานกันและคุณจะต้องขึ้นอยู่กับตัวกำหนดเวลา - หากเวลาที่ผู้อ่านของคุณ เขียนไปยังท่อทั้งสองคุณไม่สามารถบอกได้ว่ามาก่อน)

"[T] เขาสั่งให้พวกเขาเกิดขึ้น" เป็นที่รู้จักกันในแอปพลิเคชันเท่านั้น การสั่งซื้อเอาต์พุตใน stdout / stderr เป็นปัญหาที่รู้จักกันดี - คลาสสิคอาจ -


7

เมื่อคุณใช้สิ่งก่อสร้าง: 1>stdout.log 2>&1ทั้งstderrและstdoutจะถูกเปลี่ยนเส้นทางไปยังไฟล์เพราะการเปลี่ยนเส้นทางstdoutถูกตั้งค่าก่อนการเปลี่ยนเส้นทางstderr

หากคุณสลับลำดับคุณสามารถเปลี่ยนเส้นทางstdoutไปยังไฟล์แล้วคัดลอก stderrเพื่อstdoutteeเพื่อให้คุณสามารถไปยังท่อ

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!

แต่นั่นไม่อนุญาตให้คุณบันทึกทั้งสองสตรีมไปยังไฟล์เดียวกัน
artistoex

1
@artistoex: คุณสามารถใช้tee -aกับไฟล์เดียวกันที่ใช้ในการ1>หาผลรวมทั้งเอาต์พุตในไฟล์เดียวกัน สิ่งที่ต้องการ:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya

ฉันไม่รู้ว่าทำไม แต่สิ่งนี้ไม่ได้ผลwget -O - www.google.deสำหรับฉัน (เปรียบเทียบกับโซลูชันด้านล่าง)
artistoex

4
การใช้tee -aจะไม่ทำงานอย่างน่าเชื่อถือเพื่อให้ได้ผลลัพธ์เดียวกันกับที่อยู่บนคอนโซลหากไม่มีการเปลี่ยนเส้นทาง เอาต์พุตที่ส่งผ่านteeอาจล่าช้าเล็กน้อยเมื่อเทียบกับเอาต์พุตที่มาจากคำสั่งโดยตรงและอาจปรากฏในภายหลังในบันทึก
Gilles 'หยุดความชั่วร้าย'

สิ่งนี้ใช้ไม่ได้สำหรับฉันออก + err.log ว่างเปล่า และใช่ฉันต้องการ stderror และมาตรฐานในไฟล์เดียวกัน
Andreas

7

ฉันสามารถทำสิ่งนี้ได้สำเร็จโดยวางบรรทัดต่อไปนี้ที่ด้านบนสุดของสคริปต์ทุบตี:

exec 1>>log 2> >(tee -a log >&2)

นี่จะเปลี่ยนเส้นทาง stdout ไปยังไฟล์log( 1>>log) จากนั้นกด stderr ไปยังไฟล์log(2> >(tee -a log ) แล้วนำกลับไปที่ stderr ( >&2)ด้วยวิธีนี้ฉันจะได้รับไฟล์เดียวlogซึ่งแสดงทั้ง stdout และ stderr ตามลำดับและ stderr ก็เช่นกัน ปรากฏขึ้นบนหน้าจอตามปกติ

สิ่งที่จับได้คือดูเหมือนว่าจะใช้งานได้เฉพาะเมื่อฉันต่อท้ายไฟล์เท่านั้น หากฉันไม่ได้ต่อท้ายปรากฏว่าการเปลี่ยนเส้นทางทั้งสองปิดบังซึ่งกันและกันและฉันจะได้รับผลลัพธ์เพียงครั้งเดียวเท่านั้น


วิธีใดในการรับค่านี้เพื่อ "แก้ไข" บรรทัด STDERR เพื่อรวมสตริงที่กำหนดเองเพื่อให้คุณสามารถ grep ได้อย่างง่ายดาย
IMTheNachoMan

1

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

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log

สิ่งนี้ยังใช้งานไม่ได้สำหรับฉันฉันได้รับ stdout lines ก่อนจากนั้น stderr ทั้งหมดในไฟล์ 'log' ตัวอย่าง: {{echo out; echo err 1> & 2; echo out2; echo err2 1> & 2; } 2> & 1> & 3 | ที / dev / tty; }> log 3> & 1
Andreas

1
@ Andreas: โอ้, duh, teeแนะนำความล่าช้าที่นี่ด้วย ฉันไม่คิดว่าจะมีวิธีแก้ปัญหาเปลือกหอยบริสุทธิ์ ในความเป็นจริงฉันสงสัยว่ามีวิธีแก้ปัญหาหรือไม่โดยไม่ต้องแก้ไขแอปพลิเคชันในบางวิธี
Gilles 'หยุดความชั่วร้าย'

1

ในการเขียน stderr ไปที่หน้าจอและเขียนทั้ง stderr และ stdout ไปยังไฟล์ - และมีบรรทัดสำหรับ stderr และ stdout ออกมาในลำดับเดียวกันพวกเขาจะถ้าทั้งสองถูกเขียนลงบนหน้าจอ:

กลายเป็นปัญหาที่ยากโดยเฉพาะอย่างยิ่งส่วนที่เกี่ยวกับการมี "ลำดับเดียวกัน" ที่คุณคาดหวังว่าคุณจะเขียนมันลงบนหน้าจอ กล่าวง่ายๆ: เขียนแต่ละไฟล์ของตัวเองทำมายากลพื้นหลังกระบวนการเพื่อทำเครื่องหมายแต่ละบรรทัด (ในแต่ละไฟล์) ด้วยเวลาที่แน่นอนบรรทัดที่ผลิตและจากนั้น: "หาง - ติดตาม" ไฟล์ stderr ไปที่หน้าจอ แต่จะเห็นทั้ง "stderr" และ "stdout" พร้อมกัน - เรียงลำดับ - เรียงลำดับสองไฟล์ (พร้อมเครื่องหมายเวลาที่แน่นอนในแต่ละบรรทัด) เข้าด้วยกัน

รหัส:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

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

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

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

เหตุผลเดียวที่ลำดับอย่างน้อยใกล้เคียงกับสิ่งที่มันจะได้รับทั้ง stdout และ stderr ก็ไปที่หน้าจอก็คือ: ทุกบรรทัดเดียวมีการทำเครื่องหมายด้วยการประทับเวลาลงไปมิลลิวินาที

หากต้องการดู stderr บนหน้าจอขณะที่กระบวนการดำเนินไปให้ใช้สิ่งนี้:

tail -f $pth/$ffn.out

หวังว่าจะช่วยให้ใครบางคนออกมาที่นี่มานานหลังจากถามคำถามเดิม


+1 สำหรับความพยายามที่แท้จริงฉันมีความคิดคล้ายกัน (การประทับเวลาทั้งสองสตรีมผ่าน Perl) แต่กำลังดิ้นรนที่จะใช้งาน ฉันจะตรวจสอบว่ามันเหมาะกับฉันหรือไม่ (เช่นถ้าเก็บคำสั่งเอาท์พุทจริง ๆ เพราะโซลูชันอื่น ๆ ที่นี่จะเปลี่ยนสิ่งเล็กน้อยเนื่องจากความไม่แน่นอนระหว่างการติดแท็ก stderr และ stdout)
Olivier Dulac

0

อนุญาตfเป็นคำสั่งที่คุณต้องการให้ดำเนินการ

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

ควรให้สิ่งที่คุณต้องการ ตัวอย่างเช่นwget -O - www.google.deจะมีลักษณะเช่นนี้:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )

1
มันเกือบจะได้ผลสำหรับฉัน แต่ในล็อกไฟล์ฉันจะได้รับ stdout ทุกบรรทัดก่อนแล้วตามด้วย stderror line ทั้งหมด ฉันต้องการให้พวกเขาสอดแทรกในระเบียบตามธรรมชาติเมื่อเกิดขึ้น
Andreas

0

เมื่อพิจารณาจากสิ่งที่ฉันได้อ่านที่นี่ทางออกเดียวที่ฉันเห็นคือการเพิ่มแต่ละบรรทัดไม่ว่าจะเป็น STOUT หรือ STERR ด้วย timeslice / timestamp วันที่ +% s จะไปที่วินาที แต่ก็ช้าเพื่อรักษาความสงบเรียบร้อย นอกจากนี้ยังต้องมีการจัดเรียงบันทึก ทำงานมากกว่าที่ฉันสามารถทำได้


0

ฉันต้องดิ้นรนกับข้อกำหนดที่แน่นอนนี้และในที่สุดก็ไม่สามารถหาทางออกที่ง่ายได้ สิ่งที่ฉันทำแทนคือ:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

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

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log

0

stderr แทนคำสั่งtee -aและ stdout ต่อท้ายไฟล์เดียวกัน:

./script.sh 2> >(tee -a outputfile) >>outputfile

และหมายเหตุ: ให้มั่นใจในลำดับที่ถูกต้อง (แต่ไม่มี stderr-show) มีคำสั่ง 'unbuffer' จากเครื่องมือ 'คาดหวัง' ที่จำลอง tty และเก็บ stdout / err ตามลำดับเพื่อแสดงบนเทอร์มินัล:

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