web-dev-qa-db-ja.com

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

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

a=$(func)
b=???

一時ファイルを作成せずにstderrをb変数にリダイレクトしたいのですが。

 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

だから問題は、一時ファイルを必要とせずに bash 関数stderrfuncを変数bにリダイレクトするにはどうすればよいですか?

5
smarber

Linuxおよびシェル(ここでは、bashのように書き込み可能な一時ファイルを含む)を実装するシェルで、次のことができます。

{
  out=$(
    chmod u+w /dev/fd/3 && # needed for bash5+
      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 /xは、stdoutとstderrの両方で何かを出力するコマンドの例です)。

zshを使用すると、次のこともできます。

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

=(cmd)は一時ファイルを使用するプロセス置換の形式であり、(){ code; } args匿名関数です)。

いずれの場合も、一時ファイルを使用します。パイプを使用するソリューションでは、出力が大きい場合にデッドロックが発生しやすくなります。 2つの別々のパイプを介してstdoutとstderrを読み取り、select()/poll()といくつかのループ内の読み取りを使用して、ロックアップを引き起こさずに2つのパイプからデータを読み取ることができますが、かなり関与していて、AFAIK、zshだけがselect()サポートを組み込み、yashだけが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

)

これは、GNU grepであり、行が十分に短いことを前提としています。PIPEBUF(Linuxでは4K)より大きい行では、2つのgrepsの出力の行がチャンクにまとまって破損する可能性があります。

5

まあ、一時ファイルなしでstderrを1つの変数にキャプチャし、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にリダイレクトし、両方を1つの変数に区切り記号を付けて入れ、2つの部分に分割します。

プラス:

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

ここから取得 http://mywiki.wooledge.org/BashFAQ/002

2
smarber