評価:$?と ${PIPESTATUS[@]}(bash)

評価:$?と ${PIPESTATUS[@]}(bash)

Bash 5.0では、キャプチャを${PIPESTATUS[@]}通過したいと思いますeval。しかし、マスクはマスクと同じではないようevalです。結果から抽出する方法はありますか?以下のコマンド文字列に何かを追加しても機能しないようです。${PIPESTATUS[@]}$?${PIPESTATUS[-1]}${PIPESTATUS[@]}eval&& array=( ${PIPESTATUS[@]} ) && export array

$?これが単純ではないと仮定するのは正しいですか${PIPESTATUS[-1]}

私のサンプルコード(rootとして実行):

#!/usr/bin/env bash

#without eval, ${PIPESTATUS[@]} has two entries as it should.
apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log 
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
    echo $status
done

echo ""
echo ""

#with eval, ${PIPESTATUS[@]} has one entry and is equal to $?
commandstring="apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log"
eval "$commandstring"
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
    echo $status
done

編集:PIPESTATUSの元の説明でいくつかの技術的な修正を修正しました。また、以下の回答に基づいていくつかの説明があります。

  • プログラムでコマンド文字列を作成しているため、これを使用していますeval。その中には、bash -c他のユーザーとしてコマンドを実行してください。
  • 設定を見たpipefail結果、時には機能することがありますが、必ずしも機能するわけではありません。パイプラインのさまざまな段階の状態を知る必要がある場合もあるからです。設定set -o pipefailしてから設定を解除できるとうまくいきますが、設定を解除する方法がわからないので、単にサブシェルを終了し、未設定の新しいサブシェルに進むことはpipefailできません。pipefailたとえば、シェルオプションの設定を解除するにはpipefail
  • 埋め込み配列をエクスポートする方法の上記の理解が間違っていますかPIPESTATUS?サブシェルPIPESTATUSからどのように簡単にエクスポートできますか?eval

編集2:以下に示す素晴らしい答えのおかげで、最終的な決定は、リダイレクトを含むコマンド文字列を動的に組み合わせる状況(評価が必要な理由)を使用し、set -o pipefail実行後に実行することでしたset +o pipefail

また、前回から数年間のbash経験があるため、動的ビルドコマンドを実行するためのベストプラクティスを再検討しています。私のユースケースevalは次のとおりです

  1. コマンドにが含まれる可能性があるため、これを実行するためbash -cに使用することはできません。bash -c
  2. リダイレクトを含めることができます。>> log
  3. 動的コマンドは、デバッグ目的でパラメータを追加することもできます。

私が知っている限り、1では別のことを行うことができ、3ではとのようなものをset -x [command]使用trapする必要があります。 2の解決策はまだ見つかりませんでした。

ベストアンサー1

$?最後のコマンド実行の最も右側のコンポーネント(または設定されている場合は最も右側の失敗したコンポーネント)の状態を含みますpipefail。ただし、これは!prefixキーワードで変更できます。

要素は$PIPESTATUS前のパイプラインの各コンポーネントの終了状態であり、!値には影響しません。

それ以降は(exit 1) | (exit 2) | (exit 3)$PIPESTATUS1 2 3)になり、$?3になります。それ以降は! (exit 1) | (exit 2) | (exit 3)同じです$PIPESTATUSが、$?0になっています$(( ! 3 ))

falseまたは(exit 1)まだ1つのコンポーネントを持つパイプです。最初のケースは単純なコマンドで、2番目のケースはサブシェルなので、PIPESTATUS=(1)and $?= 1を取得します。

これは同じです。eval 'any shell code'ただ単純なコマンドです。

これには、eval 'A | B' | C終了$PIPESTATUS状態eval(最後に実行したコマンドの終了状態)Bと終了状態が含まれますC(A | B) | Cところで、同様です。

次のことができます。

eval '
  A | B
  saved_STATUS=$? saved_PIPESTATUS=( "${PIPESTATUS[@]}" )
'

呼び出すインタプリタでわかるように、パイプラインコンポーネントの状態を維持したいので、evalあなたの場合は次のようにします。

preserve_status='
  saved_STATUS=$? saved_PIPESTATUS=( "${PIPESTATUS[@]}" )
'
eval "$commandstring $preserve_status"
commandsPipestatus=( "${saved_PIPESTATUS[@]}" )
for status in "${commandsPipestatus[@]}"; do
    echo "$status"
done

しかし、ここでは以下が欲しいようです。

install_java() (
  set -o pipefail
  apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log
)

それは:

  • 変数ではなく関数にコードを保存する
  • 関数が失敗したり失敗した場合に失敗を返すには、pipefailこのオプションを使用します。関数本体は、より一般的なサブシェルではなく、サブシェルです。apt-getteeコマンドグループcommand( { ...; }) は、オプションがローカルでのみ設定されていることを示します。最新バージョンでは、サブシェルなしで関数にローカルに設定されたオプションをbash使用することもできます。local -; set -o pipefail

ただし、通常の出力とエラーの両方がapt-getstdoutに送信されます。

を使用している場合は、zsh次のことができます。

exec {log}>> ~/log # at the beginning of the script for instance.
apt-get install -y java-17-openjdk-amd64 >&1 >&$log 2>&2 2>&$log

stderrのstdoutをapt-getログとその元の宛先に送信します。内部的にはingが完了し、終了tee状態が維持されます。apt-get

次のように、このためのヘルパー関数を作成することもできます。

#! /bin/zsh -
die() { print -ru2 -C1 -- "$@"; exit 1; }
exec {log}>> ~/log
with_log() { "$@" >&1 >&$log 2>&2 2>&$log; }

with_log apt-get ... || die "Aborting..."

おすすめ記事