เปลี่ยนเส้นทาง stderr ของคำสั่งต่อมาทั้งหมดโดยใช้ exec


42

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

ฉันไม่ต้องการเพิ่ม2>&1 | tee -a $DEBUGคำสั่งทุกคำสั่งในไฟล์ | tee -a $DEBUGฉันจะอยู่กับ

exec 2>&1ผมยังจำได้ไม่มีทางที่จะทำกับสิ่งที่ต้องการ

ขณะนี้ฉันกำลังใช้สิ่งต่อไปนี้:

#!/bin/bash
DEBUGLOG=/tmp/debug
exec 2>&1
somecommand | tee -a $DEBUGLOG
somecommand2 | tee -a $DEBUGLOG
somecommand3 | tee -a $DEBUGLOG

แต่มันไม่ทำงาน ใครบ้างมีวิธีแก้ไข / สามารถอธิบายสาเหตุได้


1
ในเชลล์บางตัว|&ทำงานเป็นทางลัดเพราะ2>&1 |อย่างน้อยก็สะดวกกว่าเล็กน้อย
Kevin

คำตอบ:


39

สำหรับวิธีการเปลี่ยนเส้นทางคำสั่งจำนวนมากในครั้งเดียว:

#!/bin/bash
{
    somecommand 
    somecommand2
    somecommand3
} 2>&1 | tee -a $DEBUGLOG

สาเหตุที่โซลูชันดั้งเดิมของคุณใช้งานไม่ได้: exec 2> & 1 จะเปลี่ยนเส้นทางข้อผิดพลาดมาตรฐานไปยังเอาต์พุตมาตรฐานของเชลล์ซึ่งถ้าคุณเรียกใช้สคริปต์จากคอนโซลจะเป็นคอนโซลของคุณ การเปลี่ยนเส้นทางไปป์บนคำสั่งจะเปลี่ยนเส้นทางเอาต์พุตมาตรฐานของคำสั่งเท่านั้น

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

วิธีหนึ่งที่จะอธิบายได้อย่างแท้จริงคือดูว่าเกิดอะไรขึ้นจริง:

สภาพแวดล้อมดั้งเดิมของเชลล์ของคุณอาจมีลักษณะเช่นนี้หากคุณเรียกใช้จากเทอร์มินัล:

stdin -> /dev/pts/42
stdout -> /dev/pts/42
stderr -> /dev/pts/42

หลังจากที่คุณเปลี่ยนเส้นทางข้อผิดพลาดมาตรฐานไปยังเอาต์พุตมาตรฐาน ( exec 2>&1) คุณจะไม่เปลี่ยนแปลงอะไรเลย แต่ถ้าคุณเปลี่ยนเส้นทางการส่งออกมาตรฐานของสคริปต์ไปยังไฟล์คุณจะพบกับสภาพแวดล้อมเช่นนี้:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /dev/pts/42

จากนั้นการเปลี่ยนเส้นทางข้อผิดพลาดมาตรฐานของเชลล์ไปที่เอาต์พุตมาตรฐานจะเป็นดังนี้:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /your/file

การรันคำสั่งจะสืบทอดสภาพแวดล้อมนี้ หากคุณรันคำสั่งและไพพ์มันเพื่อทีสภาพแวดล้อมของคำสั่งจะเป็น:

stdin -> /dev/pts/42
stdout -> pipe:[4242]
stderr -> /your/file

ดังนั้นข้อผิดพลาดมาตรฐานของคำสั่งของคุณยังคงเป็นสิ่งที่เชลล์ใช้เป็นข้อผิดพลาดมาตรฐาน

คุณสามารถเห็นสภาพแวดล้อมของคำสั่งจริงโดยดูใน/proc/[pid]/fd: ใช้ls -lเพื่อแสดงรายการเนื้อหาของลิงก์สัญลักษณ์ 0ไฟล์ที่นี่เป็นที่เข้ามาตรฐาน1คือส่งออกมาตรฐานและ2เป็นข้อผิดพลาดมาตรฐาน หากคำสั่งเปิดไฟล์เพิ่มเติม (และโปรแกรมส่วนใหญ่ทำ) คุณจะเห็นไฟล์เหล่านั้นด้วย โปรแกรมยังสามารถเลือกที่จะเปลี่ยนเส้นทางหรือปิดการป้อนข้อมูลมาตรฐาน / ส่งออกและนำมาใช้ใหม่0, และ12


40

คุณสามารถใช้ exec แบบนี้ที่ด้านบนสุดของสคริปต์ของคุณ:

exec > >(tee "$HOME/somefile.log") 2>&1

ตัวอย่างเช่น:

#!/bin/bash -

exec > >(tee "$HOME/somefile.log") 2>&1

echo "$HOME"
echo hi
date
date +%F
echo bye 1>&2

ให้ฉันส่งออกไปยังไฟล์$HOME/somefile.logและไปยัง terminal เช่นนี้

/home/saml
hi
Sun Jan 20 13:54:17 EST 2013
2013-01-20
bye

2
โปรดทราบว่านี่ใช้ bashisms - มันอาจไม่ทำงานใน shell อื่น ๆ (เช่น dash) แต่เนื่องจากคำถามที่ระบุทุบตี +1
Richard Hansen

8
@RichardHansen ทดแทนกระบวนการเป็นคุณลักษณะที่ได้รับการแนะนำโดย ksh ไม่ทุบตีและยังได้รับการสนับสนุนโดย zsh ดังนั้นฉันจะไม่เรียกว่าเป็นbashism
Stéphane Chazelas

6
@StephaneChazelas: คุณทำคะแนนได้ดี ฉันแค่อยากจะชี้ให้เห็นว่าไวยากรณ์ไม่ได้รับการสนับสนุนโดย POSIX มาตรฐานและดังนั้นจะไม่ทำงานในระดับสากลใน/bin/shสคริปต์ (หลาย ๆ คนใช้ผิดไวยากรณ์ bash ใน/bin/shสคริปต์)
Richard Hansen

สำหรับฉันมันให้/dev/fd/62: Operation not supportedเบาะแสใด ๆ
Eun

1
มีวิธีที่จะไม่เปลี่ยนเส้นทาง stderr ยกเว้นไฟล์บันทึกหรือไม่ หากสคริปต์ต้นฉบับคือmyscriptและฉันทำงาน./myscript > /dev/nullฉันควรจะเห็นbyeว่ามาจากecho bye >&2ไหน
Martin Jambon

0

เขียน stderr และ stdout ไปยังไฟล์แสดง stderr บนหน้าจอ (บน stdout)

exec 2> >(tee -a -i "$HOME/somefile.log")
exec >> "$HOME/somefile.log"

มีประโยชน์สำหรับ crons ดังนั้นคุณสามารถรับข้อผิดพลาด (และข้อผิดพลาดเท่านั้น) ทางอีเมล

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