一時ファイルなしでSTDERRとSTDOUTを別の変数にリダイレクトする

一時ファイルなしでSTDERRとSTDOUTを別の変数にリダイレクトする
func() {
    echo 'hello'
    echo 'This is an error' >&2
}

a=$(func)
b=???

b一時ファイルを生成せずにstderrを変数にリダイレクトしたいと思います。

 echo $b
 # output should be: "This is an error"

動作しますが、一時ファイルを使用するソリューション:

touch temp.txt
exec 3< temp.txt
a=$(func 2> temp.txt);
cat <&3
rm temp.txt

問題はどのようにstderrリダイレクトしますか?一時ファイルなしでfunc変数に機能を適用しますか?b

答え1

zsh書き込み可能な一時ファイルを使用して、Linuxおよびここにある文書(たとえば、5.1または5.1より前)を実装するシェルでbash次のことを実行できます。

{
  out=$(
    chmod u+w /dev/fd/3 && # needed for bash5.0
      ls /dev/null /x 2> /dev/fd/3
  )
  status=$?
  err=$(cat<&3)
} 3<<EOF
EOF

printf '%s=<%s>\n' out "$out" err "$err" status "$status"

ls /dev/null /xstdoutとstderrに何かを印刷するためのコマンド例はどこにありますか?)

を使用すると、zsh次の操作も実行できます。

(){ out=$(ls /dev/null /x 2> $1) status=$? err=$(<$1);} =(:)

=(cmd)一時ファイルと匿名関数を使用したプロセス置換形式です(){ code; } args。)

とにかく一時ファイルを使用したいと思います。パイプを使用するすべてのソリューションは、出力が大きいとデッドロックが発生しやすくなります。 2つの別々のパイプを介してstdoutとstderrを読み込み、ループからselect()/poll()およびいくつかの読み取りを使用してロックを生成せずに2つのパイプからデータを読み取ることができますが、これは非常に複雑で、AFAIKは組み込みサポートのみがzsh可能select()で、yashrawインターフェイスは1つだけです。pipe()(詳細は参照シェルリダイレクトを使用して同じファイル記述子を読み書きする)。

別のアプローチは、ストリームの1つを一時ファイルの代わりに一時メモリに格納することである。次のような(zshまたはbash構文):

{
  IFS= read -rd '' err
  IFS= read -rd '' out
  IFS= read -rd '' status
} < <({ out=$(ls /dev/null /x); } 2>&1; printf '\0%s' "$out" "$?")

(コマンドがNULを出力しないと仮定)

これには$err末尾の改行が含まれます。

別のアプローチは、stdoutとstderrを別々に装飾し、読み取るときに装飾を削除することです。

out= err= status=
while IFS= read -r line; do
  case $line in
    (out:*)    out=$out${line#out:}$'\n';;
    (err:*)    err=$err${line#err:}$'\n';;
    (status:*) status=${line#status:};;
  esac
done < <(
  {
    {
      ls /dev/null /x |
        grep --label=out --line-buffered -H '^' >&3
      echo >&3 "status:${PIPESTATUS[0]}" # $pipestatus[1] in zsh
    } 2>&1 |
      grep --label=err --line-buffered -H '^'
  } 3>&1

)

GNUgrepと行が十分に短いとします。ラインがPIPEBUF(Linuxの場合4K)より大きい場合、2秒の出力ラインが最終的にチャンクに分割される可能性grepがあります。

答え2

まあ、一時ファイルなしである変数からstderrをキャプチャし、別の変数からstdoutをキャプチャするのは簡単な作業ではありません。

これは有効な例です

func() {
    echo 'hello'
    echo 'This is an error' >&2
}

result=$(
    { stdout=$(func) ; } 2>&1
    echo -e "mysuperuniqueseparator\n"
    echo -e "${stdout}\n"
)
var_out=${result#*mysuperuniqueseparator$'\n'}
var_err=${result%$'\n'mysuperuniqueseparator*}

私はこれがstderrをstdoutにリダイレクトし、2つの変数を区切り文字を使用して変数に入れてから2つの部分に分割する汚れた方法なので、満足できません。

追加する:

明らかに、これはコマンドの標準出力または標準エラーに使用する区切り文字列を含む可能性があるため、信頼できません。

ここからインポートhttp://mywiki.wooledge.org/BashFAQ/002

答え3

一時ファイル/fifo、興味深い評価/ファイル記述子などはありません。

x=$((echo 'this is stdout'; echo 'this is stderr' 1>&2; exit 123) 2> >(sed -r 's/^/2/g') 1> >(sed -r 's/^/1/g'))

echo $? ### exit code is preserved
# 123

echo "$x" | sed '/^2/d;s/^1//g'  ### stdout
# this is stdout

echo "$x" | sed '/^1/d;s/^2//g'  ### stderr
# this is stderr

注:大規模出力には効率的ではない可能性があります。

関連情報