このコードは次のエラーで終了します。
(
ssh localhost seq 100000
seq 100000
) | wc
#-> seq: write error: Resource temporarily unavailable
書き込みエラーを再現するための最小限のコードです。要点は、適切に機能するように子プロセス/パイプラインアーキテクチャを変更するのではなく、後で大規模出力を作成するために再利用されるパイプにfd 1を割り当てるときにこのエラーが発生する理由を理解することです。
SSHクライアントがstdoutファイル記述子を汚すのはなぜですか?これは設計上の欠陥ですか?他のプロセスのように動作させるオプションはありますか?
編集する:コメントの手がかりによると、OpenSSHバージョン7.9〜8.4と関連があると疑われます。
答え1
これがバージョンごとの回帰による強力な手がかりに基づいて、OpenSSHバージョンに依存しないコードを取得する3つの方法があります。
fd 1のみが影響を受け、fd 0と2はクリーンなままであることが確認されており、fd 1と2の間の配置が可能な解決策です。以下のコードでは、fd 2は汚れていますが、十分なデータが書き込まれていないと欠陥は発生しませんが、fd 1はきれいです。
(
ssh localhost '(seq 100000) {safe}>&2 2>&1 >&$safe' \
{safe}>&2 2>&1 >&$safe
seq 100000
) | wc
または、追加のパイプを使用して子プロセスでsshをラップして、影響を受けるファイル記述子を分離します。
(
ssh localhost seq 100000 | cat
seq 100000
) | wc
stdoutファイル記述子を共有でき、影響を受けるOpenSSHバージョンに依存しないSSH呼び出しを作成する必要がある場合、これは許容される欠点になる可能性があります。
3番目のアプローチは、影響を受けたfdをパイプに直接割り当てるのではなく、ファイルに割り当てることです。これを行う関数は次のとおりです。
repipe(){
tmp=$(mktemp)
trap "rm -f $tmp" RETURN
"$@" > $tmp &
tail --pid $! -n+0 -f $tmp
}
(
repipe ssh localhost seq 100000
seq 100000
) | wc
編集する: https://bugzilla.mindrot.org/show_bug.cgi?id=3280
バージョン8.5(ただし少なくとも7.9にはあります)で報告され、バージョン8.9で終了しました。バイナリをアップグレードできない場合は、ssh
ユーザーのPATHの前にデフォルトの名前でこのスクリプトを配置する必要があります。/usr/bin
#!/bin/bash
native=/usr/bin/ssh
if [[ ${vnum:=$($native -V 2>&1 | (IFS="[_. ]" read osef maj min osef && printf "%02d.%s" $maj $min))} > 07.9 && $vnum < 08.9 ]]
then
exec $native "$@" | cat
else
[[ $vnum > 08.9 ]] && echo "INFO: $0 may be removed on this host">&2
exec $native "$@"
fi