Bash

如何擷取修改目前環境的函式的標準錯誤?

  • October 30, 2019

Environment Modules 1包的module功能通過修改目前shell 程序的各種環境變數來完成它的工作。

不幸的是,無論成功與否,這個函式都會返回 0 2,這使得客戶端腳本很難對失敗做出適當的響應。

我想實現一個函式包裝器mymodulemodule它將所有參數直接傳遞給,如果失敗module,則正確返回一個非零值。module

mymodule可以檢測是否module失敗的唯一方法是檢查輸出module函式寫入 stderr(如果有)。

問題是我無法想出一個合理的方法mymodule來獲得這個輸出而不使module’ 的動作無效。更具體地說,幾乎所有我能想到的將module‘stderr 擷取到變數中的方法都需要module在子程序中執行,從而阻止它完成它的工作(這需要修改目前的 shell)。

上述的一個例外是將module’s stderr 重定向到一個臨時文件,但我討厭每次module函式執行時都創建一個文件的想法。

有沒有辦法在目前環境mymodule中呼叫module,同時在變數中擷取它的標準錯誤?

我對 和 的答案都很zsh感興趣bash


1不要與Lmod 環境模組包混淆,後者俱有非常相似的界面。

2至少對於我必須使用的古老版本 3.2.9 來說是這樣。我無法控制這一點。

Bash 和 zsh 都有協同程序(不幸的是略有不同),它們本質上封裝了一個pipe呼叫並產生一個子程序,其標準輸入和標準輸出都可供呼叫 shell 使用。從本質上講,它們讓 shell 在xy中執行x | cmd | y,但所有xy和都cmd在目前程序中執行,而不是在管道執行環境中執行。

這將讓我們執行module 2>&...一些...module從目前 shell 執行。如果我們使用cat作為我們的協同程序(即cmd),它只會再次直接重複所有內容,然後我們可以稍後再次將輸出讀回目前 shell y <&...

另一種選擇是將標準錯誤重定向到另一個後台程序並wait為其返回程式碼。我將在下面解決這兩個問題。


我將使用這個偽module函式進行測試,以便我可以隨意打開和關閉錯誤。if 修改目前的 shell 環境以便我們可以看到它並將“err”輸出到 stderr;我將根據需要評論該行:

module() {
       sleep 1
       FOO=$(date)
       echo err >&2
} 

如果我cat在 Bash 中執行一個協同程序,我可以將module’s stderr 重定向到其中,並從cat’s stdout 讀取以執行我想要的任何操作:

coproc cat
module 2>&${COPROC[1]}
exec {COPROC[1]}>&-
if grep -q err <&${COPROC[0]}
then
       echo got an error
else
       echo no error
fi

在 zsh 中它需要是

module 2>&p
exec 4<&p
coproc :
if grep -q err <&4

而是在中間。

無論哪種情況,我都可以module在目前 shell 中執行命令並從那裡讀取錯誤輸出。函式可以if正常返回。

cat除了在目前執行環境中執行之外的所有內容:FD 重定向不會像管道那樣創建獨立的環境。我們可以echo $FOO在最後檢查一下,並看到日期已經更新,因為module在目前環境中執行。


或者,後台程序可以完成所有工作。這在 Bash 中有效:

exec 2> >( if grep -q . ; then exit 7 ; else exit 0 ; fi )
PID=$!
module
exec 2>&-
wait $PID
echo $?

以上將輸出70根據頂部子程序所說的內容 - 您可以調整以對返回碼做任何您喜歡的事情。在 zsh 下它沒有,因為$!沒有為程序替換設置;這應該是可以解決的,但我停止了嘗試。固定的先進先出,而不是臨時文件,也可以在這里工作。

在這種情況下,您可能還希望在任一側保存和恢復 FD 2。

引用自:https://unix.stackexchange.com/questions/525203