
$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проигралиที่นี่ fortune โปรแกรมแบบมีเงื่อนไขโดยไม่ต้อง exit(rand()).
คุณสามารถอธิบาย? เกิดอะไรขึ้นที่นี่?
การพูดนอกเรื่องโคลงสั้น ๆ - ประวัติศาสตร์
ฉันคุ้นเคยกับ Heisenbug นี้เป็นครั้งแรกเมื่อหนึ่งในสี่ของศตวรรษที่ผ่านมา จากนั้นสำหรับเกตเวย์ใน FaxNET จำเป็นต้องสร้างยูทิลิตี้หลายอย่างผ่าน "การเล่นหมากฮอส" ภายใต้ FreeBSD ตามที่คาดไว้ ฉันคิดว่าตัวเองเป็นโปรแกรมเมอร์ขั้นสูงและมีประสบการณ์พอสมควร ดังนั้นฉันจึงตั้งใจทำทุกอย่างอย่างรอบคอบและระมัดระวังที่สุดเท่าที่จะเป็นไปได้ โดยให้ความสนใจเป็นพิเศษกับการจัดการข้อผิดพลาด...
ประสบการณ์ก่อนหน้าของฉันในการจัดการกับจุดบกพร่องใน sendmail และ uucp/uupc ได้เพิ่มความขยันหมั่นเพียรของฉันในเรื่อง “การจัดการข้อผิดพลาดอย่างละเอียด” ไม่มีประโยชน์ที่จะเจาะลึกรายละเอียดของเรื่องราวนั้น แต่ฉันต่อสู้กับ Heisenbug นี้เป็นเวลาสองสัปดาห์เป็นเวลา 10-14 ชั่วโมง จึงจำขึ้นใจและเมื่อวานคนรู้จักเก่าคนนี้ก็แวะมาเยี่ยมอีกครั้ง
TL; DR คำตอบ
คุณประโยชน์ head สามารถ ปิดช่องจาก fortune ทันที ทันทีที่เขาอ่านบรรทัดแรก ถ้า fortune เอาต์พุตมากกว่าหนึ่งบรรทัด จากนั้นจึงเป็นการโทรที่สอดคล้องกัน write() จะส่งคืนข้อผิดพลาดหรือรายงานว่าเอาต์พุตมีจำนวนไบต์น้อยกว่าที่ร้องขอ ในทางกลับกันเขียนด้วยการจัดการข้อผิดพลาดอย่างระมัดระวัง fortune มีสิทธิที่จะสะท้อนสถานการณ์นี้ในสถานะทางออก แล้วเนื่องจากการติดตั้ง set -o pipefail จะทำงาน || echo "Вы проиграли".
อย่างไรก็ตาม head อาจจะมาไม่ทัน ปิดก่อน fortune จะเสร็จสิ้นการส่งออกข้อมูล จากนั้นมันจะทำงาน && echo "Повезло!".
หนึ่งในวันนี้ของฉัน มีดังกล่าว :
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'แปลเป็นมนุษย์แล้ว
เป็นเรื่องปกติสำหรับที่นี่ и ในวิธีคอมไพเลอร์โดยใช้ตัวเลือก --version มันจะถามว่าเขาเป็นใคร และหากตัวเลือกนี้ไม่ได้รับการสนับสนุน ก็จะแทรกต้นขั้วเข้าไป "โปรดใช้คอมไพเลอร์ที่เข้ากันได้กับ GCC หรือ CLANG".
เช่น สามารถพบได้ทุกที่ มันปรากฏตัวในสถานที่นี้เมื่อนานมาแล้วและทำงานได้อย่างสมบูรณ์แบบทุกที่ (Linux, Solaris, OSX, FreeBSD, ฯลฯ) แต่เมื่อวานใน บนแพลตฟอร์ม ฉันสังเกตเห็น:
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"พูดตามตรงว่าฉันจำ "คนรู้จัก" เก่าของฉันในทันทีไม่ได้ นอกจากนี้ โปรเจ็กต์นี้ได้รับการทดสอบหลายครั้งบน Elbrus และภายใต้การแจกแจงที่แตกต่างกันมากมาย รวมถึง Alt ด้วยคอมไพเลอร์ที่หลากหลาย เวอร์ชันของ GNU Make และ bash ดังนั้นฉันไม่ต้องการที่จะเห็นความผิดพลาดของฉันที่นี่
เมื่อพยายามจำลองปัญหาและ/หรือทำความเข้าใจกับสิ่งที่เกิดขึ้น สิ่งแปลกๆ ก็เริ่มเกิดขึ้น
สะกดบรรทัดคำสั่ง:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"บางครั้งมันจะสร้างข้อความเพิ่มเติม แต่ก็ไม่... บ่อยครั้งที่ตัวเลือกใดตัวเลือกหนึ่งจะอยู่ได้ค่อนข้างนาน แต่ถ้าคุณกระตุ้นนานกว่านี้ คุณจะได้ทั้งสองอย่างเสมอ!
แน่นอน ทุกอย่างของเรา! และเมื่อพิมพ์คำด่า strace แต่ไม่มีเวลากด Enter ฉันจำเพื่อนเก่าของฉัน Mr. Heisenbug และนักพัฒนาได้ ตัวฉันเองเมื่อ 25 ปีที่แล้ว และฉันตัดสินใจที่จะเศร้าและเขียนบันทึกนี้ 😉
โดยวิธีการเช่นเดียวกับการเคารพตนเอง , ภายใต้ strace ไม่ชอบที่จะสืบพันธุ์
เกิดอะไรขึ้น?
- คุณประโยชน์
headมีสิทธิ์ (หรือมากกว่านั้นคือถูกบังคับ) เพื่อปิดช่องที่กำลังอ่านทันทีที่อ่านตามจำนวนบรรทัดที่ร้องขอ - ผู้เขียนโปรแกรมสร้างข้อมูล (ในกรณีนี้คือ
cc) สามารถ พิมพ์หลายบรรทัดและ ฟรี ทำสิ่งนี้ผ่านการโทรหลายครั้งwrite(). - ถ้า ผู้อ่านจะมีเวลาปิดช่องฝั่งตนก่อนจบการบันทึกฝั่งผู้เขียน จากนั้น ผู้เขียนจะได้รับข้อผิดพลาด
- โปรแกรมนักเขียน สิทธิที่จะ ทั้งสองละเว้นข้อผิดพลาดในการเขียนช่องและสะท้อนให้เห็นในโค้ดที่สมบูรณ์ของคุณ
- เนื่องจากการติดตั้ง
set -o pipefailรหัสการเสร็จสิ้นไปป์ไลน์จะไม่เป็นศูนย์ (ผิดพลาด) หากผลลัพธ์ไม่เป็นศูนย์จากองค์ประกอบอย่างน้อยหนึ่งรายการ จากนั้นจึงจะทำงานได้|| echo "Please use GCC or CLANG compatible compiler".
อาจมีการเปลี่ยนแปลงได้ขึ้นอยู่กับว่าโปรแกรมตัวเขียนทำงานกับสัญญาณอย่างไร ตัวอย่างเช่น โปรแกรมอาจยุติการทำงานอย่างผิดปกติ (ด้วยการสร้างสถานะการยกเลิกที่ไม่ใช่ศูนย์/ข้อผิดพลาดโดยอัตโนมัติ) หรือ write() จะส่งคืนผลลัพธ์ของการเขียนไบต์น้อยกว่าที่ร้องขอและตั้งค่า errno = EPIPE.
ใครผิด?
ในกรณีที่อธิบายไว้ เล็กน้อยของทุกสิ่ง. เกิดข้อผิดพลาดในการจัดการใน cc (lcc:1.23.20:Sep—4-2019:e2k-v3-linux) ไม่ใช่ ซ้ำซ้อน ในหลายกรณี เป็นการดีกว่าถ้าทำผิดโดยระมัดระวัง แม้ว่าจะเผยให้เห็นข้อบกพร่องอย่างกะทันหันในสำเร็จรูปที่ออกแบบมาเพื่อพฤติกรรมแบบดั้งเดิมก็ตาม
จะทำอย่างไร?
ไม่ถูก:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"แก้ไข:
(fortune && echo "Успешно" || echo "Ошибка") | head -1ที่นี่ การปิดไปป์ก่อนกำหนดจะได้รับการจัดการโดยล่ามคำสั่ง เมื่อให้บริการไปป์ไลน์ที่ซ้อนกัน ("ภายใน" วงเล็บ) ตามนั้น ถ้า
fortuneจะรายงานข้อผิดพลาดเป็นลายลักษณ์อักษรไปยังช่องปิดในสถานะแล้วเอาต์พุต|| echo "Ошибка"ไปไม่ถึงไหนเพราะปิดช่องไปแล้วfortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"นี่คือยูทิลิตี้
catทำหน้าที่เป็นแดมเปอร์เพราะจะละเว้นข้อผิดพลาดEPIPEเมื่อถอนตัว แค่นี้ก็เพียงพอแล้วสำหรับการสรุปfortuneเล็ก (หลายบรรทัด) และพอดีกับบัฟเฟอร์ช่องสัญญาณ (ตั้งแต่ 512 ไบต์ถึง µ64K ใน OS ส่วนใหญ่ ≥4K) มิฉะนั้นปัญหาอาจกลับมา
วิธีการประมวลผลอย่างถูกต้อง EPIPE และข้อผิดพลาดในการบันทึกอื่น ๆ ?
ไม่มีวิธีแก้ปัญหาที่ถูกต้องเพียงวิธีเดียว แต่มีคำแนะนำง่ายๆ:
EPIPEต้อง จะต้องได้รับการประมวลผล (และสะท้อนให้เห็นในสถานะการออก) เมื่อส่งออกข้อมูลที่ต้องใช้ความสมบูรณ์ ตัวอย่างเช่นระหว่างการทำงานของผู้จัดเก็บหรือยูทิลิตี้สำรองข้อมูลEPIPEดีกว่าที่จะเพิกเฉย เมื่อแสดงข้อมูลและข้อความเสริม เช่น เมื่อแสดงข้อมูลเกี่ยวกับตัวเลือกต่างๆ--helpหรือ--version.- หากโค้ดที่กำลังพัฒนาสามารถนำไปใช้ในไปป์ไลน์ก่อนได้
| headแล้วEPIPEเป็นการดีกว่าที่จะเพิกเฉย ไม่เช่นนั้นจะเป็นการดีกว่าที่จะประมวลผลและสะท้อนถึงสถานะการออก
ผมขอใช้โอกาสนี้แสดงความขอบคุณทีมงาน и เพื่อผลงานอันทรงประสิทธิภาพอันยิ่งใหญ่ ความมุ่งมั่นของคุณน่าทึ่งมาก!
สู้ต่อไป สู้ ๆ นะ !
ขอบคุณ เพื่อแก้ไขคำผิดและข้อผิดพลาด
เคดีพีวีจาก
ที่มา: will.com
