現在の環境を修正する関数の標準エラーをキャプチャする方法は?

現在の環境を修正する関数の標準エラーをキャプチャする方法は?

module機能環境モジュール1パッケージはさまざまな環境変数を変更して作業を行います。現在のシェルプロセス

残念ながら、この関数は成功したかどうかにかかわらず0 2を返すので、クライアントスクリプトは失敗に適切に対応するのが困難です。

すべてのパラメータを直接渡し、失敗時にゼロ以外の値を正しく返すmymodule関数ラッパーを実装したいと思います。modulemodulemodule

mymodule失敗を検出する唯一の方法は、module出力module関数がstderr(存在する場合)に何を書き込むかを確認することです。

問題は、ジョブをmymoduleキャンセルしないと、この出力を得るための合理的な方法を考えることができないことです。より具体的には、stderrを変数としてキャプチャするmoduleために私が考えることができるほとんどすべての方法は子プロセスで実行する必要があります。modulemodule

上記の例外はstderrを一時ファイルにリダイレクトすることですが、関数が実行されるたびにファイルを生成するという考えはmodule嫌いです。module

現在の環境でmymodule呼び出すmoduleと同時に変数に標準エラーをキャプチャする方法はありますか?

zsh私は両方の答えに興味がありますbash


1.交流しないでください。Lmod環境モジュールインターフェイスは非常に似たパッケージです。

2少なくとも私が使用しなければならなかった以前のバージョン3.2.9の場合はそうでした。私はこれを制御できません。

答え1

Bashとzshの両方共同プロセス(残念ながら少し異なります)基本的には次のように終わります。pipe電話そして、呼び出しシェルで標準入力と標準出力を使用できるサブプロセスを作成します。デフォルトでは、シェルは現在のプロセスで実行されますが、xすべて、実行されます。yx | cmd | yxycmdいいえパイプライン実行環境で。

module 2>&...これにより、現在のシェルで何かを実行できます。セカンダリプロセスとして使用すると(たとえば)、すべてを直接再度繰り返し、後で出力を現在のシェルに読み直すことができます。...modulecatcmdy <&...

別のオプションは、標準エラーを別のバックグラウンドプロセスにリダイレクトし、対応するwait戻りコードを取得することです。以下では、これら2つの問題について議論します。


この偽の機能をテスト用に使用して、勝手にエラーをオン/オフmoduleできるようにします。 ifは私たちが見ることができるように現在のシェル環境を変更し、stderrに "err"を印刷します。必要に応じてこの行に注釈を付けます。

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

catBash で共同プロセスを実行すると、moduleの stderr をその中にリダイレクトし、catの 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現在のシェルでコマンドを実行し、そこからエラー出力を読み取ることができます。関数がif正常に返される可能性があります。

cat現在の実行環境で実行されている以外はすべていいえパイプラインなどの独立した環境を作成します。echo $FOO最後にこれを確認すると、module現在の環境で実行してから日付が更新されたことを確認できます。


または、バックグラウンドプロセスはすべての操作を実行できます。これはBashで動作します:

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

上記の内容は、トップレベルの子プロセスの内容を出力するか、または7戻りコードを使用して必要に応じて実行するように調整できます。プロセス置換が設定されていない0ため、zshではそうではありません。この問題は解決可能でなければなりませんが、試みを中止しました。$!一時ファイルの代わりに固定fifoもここで動作します。

この場合、両側からFD 2を保存して復元することもできます。

答え2

「環境モジュール」がどのように機能するかはわかりませんが、説明によれば、環境変数を設定するシェル関数であり、標準エラー出力は別のプロセスで実行する必要なしにキャプチャ/一致する必要があると仮定しています。

良いか嫌いでも、信頼でき、確実な唯一の方法は、標準エラーを一時ファイルにリダイレクトすることです。名前付きパイプを使用することも同様に不便で(まだ一時ファイルを作成する必要があります!)、よりトリッキーです。コプロセスの使用は重くて面倒で持ち運びが難しい

bash(そして唯一のbash)あなたは以下を利用することができます文書化されていない機能$!プロセス交換でPIDに設定>(...))以下の状況を取り除いてください。

module 2> >(grep error)
wait $! && echo failed

この例では、それmodule自体が混乱する可能性がある子孫を作成しないと仮定しています$!

答え3

Linuxでは、bashとzshを使用して次のことができます。

my_module() {
  chmod u+w /dev/fd/3 # only needed in bash 5+
  module 2> /dev/fd/3 3>&-
  ! grep -q err /dev/fd/3
} 3<<< ''

これは3<<< ''最初に空行を含む文字列です。zsh両方ともbashここに文字列を実装し、ここで削除された一時ファイルとして文書化します。 Linux(およびCygwinですが、通常は他のシステムではありません)でファイルを開くと、/dev/fd/3fd 3が指すファイルが削除されても(他のシステムではfd 3をコピーします)、開かれるため、一時ファイルを処理する方法です。非常にきれいな方法があります。ファイルが削除されたため、クリーンアップについて心配する必要はなく、短時間でFSにのみ表示されます(ただし、バージョン5以降はそのファイルへの書き込みbash権限が削除されたため、変更する必要があります)。これchmod)。

moduleここで(別々のプロセスで並列ではなく)順次実行するには一時ファイルが必要ですgrep。パイプ(Michaelのアプローチと似ていますが、@ mosvyのアプローチとは異なります)を使用してこれを行うと、パイプを埋めるのに十分なデータ出力があるとデッドロックが発生します。

関連情報