評価:$?と ${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..."

答え2

代わりに、関数を使用する方がeval良い解決策かもしれません。

したがって、必要に応じて複数のパッケージをインストールできるようにするだけです。

install() {
  apt-get install -y "${1}" 2>&1 | tee -a ~/log
}
packages=("java-17-openjdk-amd64")
for pkg in "${packages[@]}" ; do
  install pkg
  # replace below with actions based on status
  printf "%s\n" "${PIPESTATUS[@]}"
done

set -o pipefailapt-getすべてのコマンドを確認せずにエラーを削除する方法ですが、PIPESTATUSログに書き込めない場合は、状況が若干ぼやけることがあります。tee

編集:追加情報に基づいて

私はあなたが経験している問題がevalサブシェル(または少なくともそれに似たもの)でコマンドを実行するためだと思います。終了コードだけを受け取るように感じます(家庭が間違っていて幸いです)。

コマンドからパイプの状態を印刷できますが、インポートが難しい場合があります。

このモンスターは処理する1つの方法ですが、非常に複雑で、テキスト処理の面でコマンドをいくつかカスタマイズする必要があり、最終的には終了コードのみを受け取ります。

# just some stuff to print some text and exit with a non zero
# before the next pipeline step masks the exit code

foo='bash -c "echo hello;false" | tee log;echo PIPE ${PIPESTATUS[@]}'
# awk grabs the line with the pipe status text, pulls one of
# the exit codes from it and exits with it, otherwise prints
# the output as seen

eval "$foo" | awk '
  {if ($1=="PIPE"){print "just showing you I saw all the codes "$0;exit $2}; print $0}'
echo $?

awkさまざまなパイプライン状態をキャプチャするために出力を提供するために何かをすることができるようですがreadarray、それでは非常に汚れてしまいます(すでに汚れた状態にもかかわらず)。

状況がこのように醜くなると、全体的なアプローチが間違っているかどうか疑問になり始める必要があります。

おそらく要件をさらに詳しく説明すると、これを達成するための別の方法が表示されることがあります。

関連情報