วิธีเปลี่ยนเส้นทาง stdout ไปยังไฟล์และ stdout + stderr ไปยังไฟล์อื่นได้อย่างไร


32

ฉันจะประสบความสำเร็จได้อย่างไร

cmd >> file1 2>&1 1>>file2

นั่นคือ stdout และ stderr ควรเปลี่ยนเส้นทางไปที่หนึ่งไฟล์ (file1) และ stdout เท่านั้น (file2) ควรเปลี่ยนเส้นทางไปที่อื่น (ทั้งในโหมดต่อท้าย)?

คำตอบ:


41

ปัญหาคือเมื่อคุณเปลี่ยนเส้นทางเอาต์พุตของคุณจะไม่สามารถใช้งานได้อีกในการเปลี่ยนเส้นทางครั้งต่อไป คุณสามารถteeไพพ์ไปไว้ในเชลล์ย่อยเพื่อเก็บเอาท์พุทสำหรับการเปลี่ยนเส้นทางครั้งที่สอง:

( cmd | tee -a file2 ) >> file1 2>&1

หรือถ้าคุณต้องการที่จะเห็นผลลัพธ์ใน terminal:

( cmd | tee -a file2 ) 2>&1 | tee -a file1

เพื่อหลีกเลี่ยงการเพิ่ม stderr ของคำสั่งแรกteeไปfile1คุณควรเปลี่ยน stderr ของคำสั่งของคุณไปยัง file descriptor (เช่น 3) และจากนั้นเพิ่มไฟล์นี้ใน stdout อีกครั้ง:

( 2>&3 cmd | tee -a file2 ) >> file1 3>&1
# or
( 2>&3 cmd | tee -a file2 ) 3>&1 | tee -a file1

(ขอบคุณ @ fra-san)


16

ด้วยzsh:

cmd >& out+err.log > out.log

ในโหมดต่อท้าย:

cmd >>& out+err.log >> out.log

ในzshและmult_iosตัวเลือกที่ให้ไว้ไม่ได้ถูกปิดใช้งานเมื่อตัวบ่งชี้ไฟล์ (ที่นี่ 1) ถูกเปลี่ยนเส้นทางหลายครั้งสำหรับการเขียนเชลล์จะใช้บิวด์อินteeเพื่อคัดลอกเอาต์พุตไปยังเป้าหมายทั้งหมด


ฉันไม่สามารถคิดออกว่าอะไรout+errและoutหมายถึงที่นี่ ชื่อไฟล์? สตรีมที่จะถูกเปลี่ยนเส้นทาง?
gronostaj

@gronostaj คิดว่าคำสั่งอ่านcmd >& file1 > file2
Isaac

วิธีการแก้ปัญหานี้จะรักษาลำดับในการสร้างผลลัพธ์ ในการจัดเก็บstdout และ stderr (ตามลำดับ) จริง ๆ แล้วคุณต้องใช้วิธีอื่น
Isaac

1
@Isaac คำสั่งจะไม่ถูกสงวนไว้เนื่องจากเอาต์พุต stdout จะผ่านไพพ์ (ไปยังกระบวนการที่ส่งต่อไปยังแต่ละไฟล์) ในขณะที่เอาต์พุต stderr จะไปยังไฟล์โดยตรง ไม่ว่าในกรณีใดมันไม่เหมือน OP ที่ขอให้เอาต์พุต stderr มาตามหลัง stdout อันใดอันหนึ่ง
Stéphane Chazelas

3

คุณสามารถ: tag stdout (โดยใช้ UNBUFFERED sed, เช่น:), sed -u ...stderr ก็ไปที่ stdout (ไม่ได้ใส่แท็กเพราะมันไม่ได้ผ่าน sed การติดแท็กนั้น) และสามารถแยก 2 ในไฟล์บันทึกผลลัพธ์

ต่อไปนี้: ช้า (สามารถปรับให้เหมาะสมอย่างจริงจังโดยใช้สำหรับสคริปต์ exlple แทนที่จะเป็นขณะที่ ... ; do ... ; เสร็จสิ้นสำหรับตัวอย่างที่จะวางไข่ subshells & คำสั่งที่ทุกบรรทัด!) แปลก (ดูเหมือนว่าฉันต้องการสเตจ 2 {} ในการเปลี่ยนชื่อหนึ่ง stdout แล้วเพิ่มอีก "stedr ผ่าน" stderr ลงไปในนั้น) ฯลฯ แต่มันคือ: " หลักฐานของแนวคิด " ที่จะพยายามเก็บไว้ คำสั่งของเอาท์พุทมากที่สุดของ stdout & stderr มากที่สุด:

#basic principle (some un-necessary "{}" to visually help see the layers):
# { { complex command ;} | sed -e "s/^/TAGstdout/" ;} 2>&1 | read_stdin_and_redispatch

#exemple:
# complex command = a (slowed) ls of several things (some existing, others not)
#  to see if the order of stdout&stderr is kept

#preparation, not needed for the "proof of concept", but needed for our specific exemple setup:
\rm out.file out_AND_err.file unknown unknown2 
touch existing existing2 existing3

#and the (slow, too many execs, etc) "proof of concept":
uniquetag="_stdout_" # change this to something unique, that will NOT appear in all the commands outputs... 
                     # avoid regexp characters ("+" "?" "*" etc) to make it easy to remove with another sed later on.

{
   { for f in existing unknown existing2 unknown2 existing3 ; do ls -l "$f" ; sleep 1; done ;
   } | sed -u -e "s/^/${uniquetag}/" ;
} 2>&1 | while IFS="" read -r line ; do
    case "$line" in
       ${uniquetag}*) printf "%s\n" "$line" | tee -a out_AND_err.file | sed -e "s/^${uniquetag}//" >> out.file ;; 
        *)            printf "%s\n" "$line"       >> out_AND_err.file ;;   
    esac; 
done;

# see the results:
grep "^" out.file out_AND_err.file

นี่เป็นเรื่องยากที่จะเข้าใจ (1) ทำไมคุณถึงใช้กรณีการใช้งานที่ซับซ้อนเช่นนี้ ( ls unknown) เพื่อพิมพ์บางอย่างบน stderr? >&2 echo "error"จะดี (2) teeสามารถผนวกหลายไฟล์พร้อมกันได้ (3) ทำไมไม่เพียง แต่catแทนที่จะเป็นgrep "^"? (4) สคริปต์ของคุณจะล้มเหลวเมื่อเอาท์พุท stderr _stdout_เริ่มต้นด้วย (5) ทำไม
pLumo

@RoVo: 2 บรรทัดแรกที่แสดงความคิดเห็นแสดงอัลกอริทึมง่ายกว่าการพิสูจน์ตัวอย่างแนวคิด 1): สิ่งนี้ls loopจะเอาท์พุททั้งบน stdout และ stderr, ผสม (หรือ), ในลำดับที่ควบคุม, เพื่อให้เราสามารถตรวจสอบได้ว่าเราเก็บ stderr / stdout ไว้แม้จะมีการติดแท็กของ stdout 2): gnu tail, อาจ แต่ไม่ หางปกติ (เช่นบน aix.) 3): grep "^" ยังแสดงชื่อไฟล์ทั้งสอง 4): ตัวแปรนี้สามารถเปลี่ยนแปลงได้ 5): ตัวอย่างที่ซับซ้อนที่ทำงานบน oses เก่า (เช่น aix เก่า) ที่ฉันทดสอบ (ไม่มี Perl)
Olivier Dulac

(1) echo to stderr และ stout หลายรายการจะใช้ได้ แต่ไม่เป็นไรไม่สำคัญอ่านง่ายกว่า (3) ยอมรับ (4) แน่นอน แต่มันจะล้มเหลวหากเริ่มต้นด้วยตัวแปรใดก็ตามที่มี (5) ฉันเห็น
pLumo

@RoVo ฉันเห็นด้วยกับคุณ 1) สำหรับ 4) ตัวแปรอาจซับซ้อนเท่าที่จำเป็นเพื่อทำให้ pb หายไป (เช่น: uniquetag="banaNa11F453355B28E1158D4E516A2D3EDF96B3450406... )
Olivier Dulac

1
แน่นอนว่ามันไม่น่าเป็นไปได้ แต่อย่างไรก็ตามมันอาจจะนำเสนอปัญหาด้านความปลอดภัยในภายหลัง และจากนั้นคุณอาจต้องการลบสตริงนั้นก่อนที่จะพิมพ์ไปยังไฟล์ ;-)
pLumo

2

ถ้าลำดับของเอาต์พุตต้องเป็น: stdout ดังนั้น stderr ; ไม่มีวิธีแก้ปัญหาด้วยการเปลี่ยนเส้นทางเท่านั้น
stderr ต้องถูกเก็บไว้ในไฟล์ชั่วคราว

cmd 2>>file-err | tee -a file1 >>file2
cat file-err >> file1
rm file-err

รายละเอียด:

วิธีเดียวที่จะเปลี่ยนเส้นทางหนึ่งเอาต์พุต (fd เช่น stdout หรือ stderr) ไปยังสองไฟล์คือการทำซ้ำ คำสั่งteeเป็นเครื่องมือที่ถูกต้องในการทำซ้ำเนื้อหาอธิบายไฟล์ ดังนั้นความคิดเริ่มต้นที่จะมีหนึ่งเอาต์พุตในสองไฟล์คือการใช้:

... |  tee file1 file2

นั่นจะสร้าง stdin ของทีออฟให้กับไฟล์ทั้งสอง (1 & 2) โดยปล่อยเอาต์พุตของทียังคงไม่ได้ใช้ แต่เราต้องผนวก (ใช้-a) และต้องการเพียงหนึ่งสำเนา วิธีนี้แก้ปัญหาทั้งสอง:

... | tee -a file1 >>file2

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

  1. cmd 2>>file1 | tee -a file2 >>file1
  2. cmd 2>>file1 > >( tee -a file2 >>file1 )
  3. ( cmd | tee -a file2 ) >> file1 2>&1

ตัวเลือก 2 ใช้งานได้ในเปลือกหอยบางตัวเท่านั้น ตัวเลือก 3 ใช้ subshell เพิ่มเติม (ช้ากว่า) แต่ใช้ชื่อไฟล์เพียงครั้งเดียว

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


1
หรือเก็บในหน่วยความจำเช่นเดียวกับsponge:(cmd | tee -a out >> out+err) 2>&1 | sponge >> out+err
Stéphane Chazelas

1

ในความสนใจของความหลากหลาย:

หากระบบของคุณรองรับ/dev/stderrแล้ว

(cmd | tee -a /dev/stderr) 2>> file1 >> file2

จะทำงาน. เอาต์พุตมาตรฐานของ the cmd ถูกส่งไปยัง stdout และ stderr ของไปป์ไลน์ ข้อผิดพลาดมาตรฐานของการcmdข้ามtee และออก stderr ของไปป์ไลน์

ดังนั้น

  • stdout ของไปป์ไลน์เป็นเพียง stdout ของcmdและ
  • stderr ของไปป์ไลน์คือ stdout และ stderr ของcmd, ถูกผสม

มันเป็นเรื่องง่าย ๆ ในการส่งกระแสข้อมูลเหล่านั้นไปยังไฟล์ที่ถูกต้อง

เช่นเดียวกับวิธีการเกือบทุกอย่างเช่นนี้ (รวมถึงคำตอบของStéphane ) file1อาจได้รับบรรทัดที่ไม่เป็นระเบียบ

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