เพื่อประโยชน์ของผู้อ่านสูตรนี้ค่ะ
- สามารถนำมาใช้ใหม่เป็น oneliner เพื่อจับ stderr เป็นตัวแปร
- ยังให้สิทธิ์การเข้าถึงโค้ดส่งคืนของคำสั่ง
- สังเวยตัวอธิบายไฟล์ชั่วคราว 3 (ซึ่งแน่นอนว่าคุณสามารถเปลี่ยนแปลงได้)
- และไม่แสดงตัวอธิบายไฟล์ชั่วคราวนี้ไปยังคำสั่งด้านใน
หากคุณต้องการที่จะจับstderr
ของบางส่วนcommand
เข้าไปในvar
ที่คุณสามารถทำได้
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
หลังจากนั้นคุณมีทุกอย่าง:
echo "command gives $? and stderr '$var'";
หากcommand
เป็นเรื่องง่าย (ไม่เหมือนa | b
) คุณสามารถออกจากภายใน{}
:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
ห่อลงในbash
ฟังก์ชั่นที่ใช้ซ้ำได้ง่าย(อาจต้องใช้เวอร์ชัน 3 ขึ้นไปlocal -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
อธิบาย:
local -n
นามแฝง "$ 1" (ซึ่งเป็นตัวแปรสำหรับcatch-stderr
)
3>&1
ใช้ file descriptor 3 เพื่อบันทึก stdout points
{ command; }
(หรือ "$ @") จากนั้นดำเนินการคำสั่งภายในการจับผลลัพธ์ $(..)
- โปรดทราบว่าคำสั่งที่แน่นอนมีความสำคัญที่นี่ (ทำในทางที่ผิดจะทำให้ตัวอธิบายไฟล์ผิด):
2>&1
เปลี่ยนเส้นทางstderr
ไปที่การจับภาพเอาท์พุท$(..)
1>&3
เปลี่ยนเส้นทางstdout
ออกไปจากการจับเอาท์พุท$(..)
กลับไปที่ "outer" stdout
ซึ่งถูกบันทึกไว้ใน file descriptor 3 โปรดทราบว่าstderr
ยังคงอ้างอิงถึงตำแหน่งที่ FD 1 ชี้ไปก่อนหน้า: การจับภาพเอาต์พุต$(..)
3>&-
จากนั้นปิดไฟล์ descriptor 3 เนื่องจากไม่จำเป็นอีกต่อไปเช่นที่command
ไม่ได้มีตัวอธิบายไฟล์เปิดที่ไม่รู้จักปรากฏขึ้น โปรดทราบว่าเปลือกนอกยังคงเปิด FD 3 แต่command
จะไม่เห็น
- สิ่งหลังมีความสำคัญเนื่องจากบางโปรแกรมเช่น
lvm
บ่นเกี่ยวกับตัวอธิบายไฟล์ที่ไม่คาดคิด และlvm
บ่นกับstderr
- สิ่งที่เรากำลังจะจับ!
คุณสามารถจับไฟล์ descriptor อื่น ๆ ด้วยสูตรนี้หากคุณปรับตาม ยกเว้น file descriptor 1 แน่นอน (นี่คือตรรกะการเปลี่ยนเส้นทางจะผิด แต่สำหรับ file descriptor 1 คุณสามารถใช้งานได้var=$(command)
ตามปกติ)
โปรดทราบว่าไฟล์นี้เสียสละตัวอธิบายไฟล์ 3 หากคุณต้องการตัวอธิบายไฟล์นั้นคุณสามารถเปลี่ยนหมายเลขได้ แต่ระวังให้ดีว่าบางหอย (จากปี 1980) อาจเข้าใจ99>&1
ว่าเป็นข้อโต้แย้ง9
ตามมาด้วย9>&1
(นี่ไม่ใช่ปัญหาสำหรับbash
)
โปรดทราบว่ามันไม่ใช่เรื่องง่ายที่จะทำให้ FD 3 สามารถกำหนดค่าได้ผ่านตัวแปร สิ่งนี้ทำให้สิ่งต่าง ๆ อ่านไม่ได้มาก:
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
หมายเหตุด้านความปลอดภัย:catch-var-from-fd-by-fd
ไม่ควรนำอาร์กิวเมนต์ 3 ข้อแรกไปจากบุคคลที่สาม ให้พวกเขาอย่างชัดเจนในแบบ "คงที่"
ดังนั้นไม่ไม่ไม่catch-var-from-fd-by-fd $var $fda $fdb $command
ไม่เคยทำเช่นนี้!
หากคุณบังเอิญผ่านชื่อตัวแปรอย่างน้อยก็ทำอย่างนี้:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
สิ่งนี้จะไม่ปกป้องคุณจากการถูกโจมตี แต่อย่างน้อยก็ช่วยตรวจจับและหลีกเลี่ยงข้อผิดพลาดของสคริปต์ทั่วไป
หมายเหตุ:
catch-var-from-fd-by-fd var 2 3 cmd..
เป็นเช่นเดียวกับ catch-stderr var cmd..
shift || return
เป็นเพียงวิธีการป้องกันข้อผิดพลาดน่าเกลียดในกรณีที่คุณลืมที่จะให้จำนวนที่ถูกต้องของการขัดแย้ง บางทีการยกเลิกเชลล์อาจเป็นอีกวิธีหนึ่ง (แต่สิ่งนี้ทำให้ยากต่อการทดสอบจาก commandline)
- กิจวัตรประจำวันถูกเขียนขึ้นเพื่อให้เข้าใจได้ง่ายขึ้น เราสามารถเขียนฟังก์ชั่นใหม่ได้โดยที่ไม่ต้องการ
exec
แต่ก็น่าเกลียดจริงๆ
- ประจำวันนี้สามารถเขียนใหม่สำหรับที่ไม่ได้เป็นอย่างดีดังกล่าวว่าไม่มีความจำเป็นในการ
bash
local -n
อย่างไรก็ตามคุณไม่สามารถใช้ตัวแปรท้องถิ่นและมันน่าเกลียดมาก!
- โปรดทราบว่า
eval
มีการใช้งานอย่างปลอดภัย มักeval
ถือว่าเป็นอันตราย อย่างไรก็ตามในกรณีนี้มันไม่ได้เลวร้ายยิ่งไปกว่าการใช้"$@"
(เพื่อรันคำสั่งตามอำเภอใจ) อย่างไรก็ตามโปรดใช้ข้อความที่ถูกต้องและถูกต้องดังที่แสดงไว้ที่นี่ (ไม่เช่นนั้นจะกลายเป็นอันตรายมาก )
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)