เพื่อประโยชน์ของผู้อ่านสูตรนี้ค่ะ
- สามารถนำมาใช้ใหม่เป็น 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)