$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
這裡 fortune
條件程序無 exit(rand())
.
你可以解釋嗎? 這裡出了什麼問題?
抒情歷史題外話
我第一次認識這個 Heisenbug 是在二十五年前。 然後,對於 FaxNET 中的網關,需要透過以下方式建立多個實用程序
我之前處理 sendmail 和 uucp/uupc 中的錯誤的經驗增加了我在「徹底的錯誤處理」方面的努力。 深入研究這個故事的細節是沒有意義的,但我在這個 Heisenbug 上掙扎了兩週 10-14 個小時。 於是,就記住了,昨天這位老熟人又來拜訪了。
TL;DR 答案
效用 head
可以 關閉通道 fortune
立刻 當他讀到第一行。 如果 fortune
輸出多一行,則對應調用 write()
將傳回錯誤或報告輸出的位元組數少於請求的位元組數。 反過來,用仔細的錯誤處理來寫 fortune
有權在其退出狀態中反映這種情況。 然後由於安裝 set -o pipefail
將工作 || echo "Вы проиграли"
.
但是, head
可能無法及時趕到 之前關閉 fortune
將完成數據輸出。 然後就可以了 && echo "Повезло!"
.
在我的今天之一 GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
翻譯成人類
這裡常見的是 --version
它詢問他是誰,如果不支援該選項,則會插入一個存根 “請使用 GCC 或 CLANG 相容編譯器”.
像
#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
順便說一句,就像任何有自尊心的人一樣 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
會在status中報寫入關閉通道錯誤,然後輸出|| echo "Ошибка"
它不會到達任何地方,因為通道已經關閉。 -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
這是實用程式
cat
充當阻尼器,因為它忽略了誤差EPIPE
撤回後。 現在就可以下結論了fortune
小(幾行)並且適合通道緩衝區(從 512 位元組到 ≈64K,在大多數作業系統中≥4K)。 否則問題可能會再次出現。
如何正確處理 EPIPE
以及其他錄音錯誤?
沒有單一正確的解決方案,但有一些簡單的建議:
EPIPE
需要 必須處理 (並反映在退出狀態中)當輸出需要完整性的資料時。 例如,在歸檔程式或備份實用程式的作業期間。EPIPE
最好忽略 顯示訊息和輔助訊息時。 例如,當顯示選項資訊時--help
或--version
.- 如果正在開發的程式碼之前可以在管道中使用
| head
,然後EPIPE
最好忽略,否則最好處理並反映在退出狀態中。
我想藉此機會向團隊表示感謝
繼續努力 同志們,加油
謝謝
KDPV 來自
來源: www.habr.com