8

スクリプトで、ビルトインの出力を配列変数にbash割り当てたいのですが、より良い方法が見つかりませんでしたtimes

tempnam=/tmp/aaa_$$_$RANDOM
times > ${tempnam}
mapfile -t times_a < ${tempnam}

出力を一時ファイルに書き込み、配列 times_a で読み戻します。これは、パイプラインまたは$(times)サブシェルで実行され、間違った値が返されるためです。

一時ファイルなしでより良い解決策はありますか?

4

4 に答える 4

7

解決する必要がある基本的な問題はtime、一時ファイルなしで、同じシェルで実行と変数の割り当ての両方を行う方法です。あるものの出力を別のものにパイプする、またはコマンドの出力をキャプチャするためにBashが提供するほとんどすべての方法は、サブシェルで動作する一方の側を持っています。

一時ファイルなしで実行できる 1 つの方法を次に示しますが、注意してください。これは見栄えがよくなく、他のシェルに移植できません。また、少なくとも Bash 4 が必要です。

coproc co { cat; }; times 1>&${co[1]}; eval "exec ${co[1]}>&-"; mapfile -tu ${co[0]} times_a

私はあなたのためにこれを分解します:

coproc co { cat; }

これによりコプロセスが作成されます。バックグラウンドで実行されるプロセスですが、その標準入力と標準出力 (FD ${co[0]}(標準出力cat) および${co[1]}(標準出力)) と通信するためのパイプが与えられますcat。コマンドはサブシェルで実行されるため、サブシェルで目標を実行することはできませんが (実行timesまたは変数への読み取り)、cat単純に入力を出力に渡し、そのパイプを使用して対話することができます。現在のシェルtimesとの間。mapfile

times >&${co[1]};

を実行timesし、その標準出力をcatコマンドの標準入力にリダイレクトします。

eval "exec ${co[1]}>&-"

コマンドの入力端を閉じますcat。これを行わないと、catは引き続き入力を待機し、出力を開いたままにし、それmapfileを待機し続けるため、シェルがハングします。exec、コマンドが渡されない場合、リダイレクトを現在のシェルに適用するだけです。にリダイレクトすると-、FD が閉じます。evalBashexec ${co[1]}>&-は FD をリダイレクトの一部ではなくコマンドとして解釈することに問題があるように見えるため、使用する必要があります。using を使用evalすると、その変数を最初に代入してから実行できます。

mapfile -tu ${co[0]} times_a

最後に、コプロセスの標準からデータを実際に読み取ります。timesこのシェルでとコマンドの両方を実行することがmapfileでき、一時ファイルは使用しませんでしたが、2 つのコマンド間のパイプラインとして一時プロセスを使用しました。

これには微妙なレースがあることに注意してください。これらのコマンドをすべて 1 つのコマンドとして実行するのではなく、1 つずつ実行すると、最後のコマンドが失敗します。s standard inを閉じるcatと終了し、コプロセスが終了して FD が閉じられるためです。すべてを 1 行で実行すると、実行時mapfileにコプロセスがまだ開いているほど速く実行され、パイプから読み取ることができるように見えます。しかし、私は幸運になっているかもしれません。私はこれを回避する良い方法を考え出していません。

つまり、一時ファイルを書き出すだけの方がはるかに簡単です。ファイル名を生成するために使用mktempします。スクリプトを使用している場合は、トラップを追加して、終了する前に一時ファイルを確実にクリーンアップします。

tempnam=$(mktemp)
trap "rm '$tempnam'" EXIT
times > ${tempnam}
mapfile -t times_a < ${tempnam}
于 2012-11-14T01:20:09.207 に答える
2

ブライアンの答えにより、この問題に非常に興味を持ち、競合状態のないこのソリューションにつながりました。

coproc cat;
times >&${COPROC[1]};
{
  exec {COPROC[1]}>&-;
  mapfile -t times_a;
} <&${COPROC[0]};

これは、ブライアンのソリューションと基本的な構造が非常に似ていますが、タイミングの問題が原因でおかしなビジネスが発生しないようにするための重要な違いがいくつかあります。mapfileブライアンが述べたように、コプロセスのファイル記述子が完全に閉じられてクリーンアップされる前にbash インタープリターがコマンドの実行を開始するため、彼のソリューションは通常機能しmapfileます。


基本的に、コプロセスのstdoutファイル記述子は、コプロセスのファイル記述子を閉じた直後に閉じられstdinます。コプロセスを保存する方法が必要stdoutです。

pipe の man ページには、次のものがあります。

パイプの読み取り側を参照するすべてのファイル記述子が閉じられている場合、write(2) により、呼び出しプロセスに対して SIGPIPE シグナルが生成されます。

したがって、少なくとも 1 つのファイル記述子をコプロセスに保持する必要がありますstdout。これはRedirectionsで簡単に実現できます。exec 3<&${COPROC[0]}-コプロセスのstdoutファイル記述子を新しく作成されたfd 31に移動するようなことができます。コプロセスが終了すると、そのコプロセスのファイル記述子がまだあり、stdoutそこから読み取ることができます。


これで、次のことができます。

  1. のみを行うコプロセスを作成しますcat

    coproc cat;
    
  2. をコプロセスにリダイレクトしtimesます。stdoutstdin

    times >&${COPROC[1]};
    
  3. コプロセスのstdoutファイル記述子の一時コピーを取得します。

  4. コプロセスのstdinファイル記述子を閉じます。(Bash-4.3 より前のバージョンを使用している場合は、evalBrian が使用したトリックを使用できます。)

    exec 3<&${COPROC[0]} {COPROC[1]}>&-;
    
  5. 一時ファイル記述子から変数に読み込みます。

    mapfile -tu 3 times_a;
    
  6. 一時ファイル記述子を閉じます (必須ではありませんが、良い方法です)。

    exec 3<&-;
    

これで完了です。ただし、物事をよりきちんとするために再構築する機会はまだいくつかあります。リダイレクト構文の性質により、このコードは次のようになります。

coproc cat;
times >&${COPROC[1]};
exec 3<&${COPROC[0]} {COPROC[1]}>&-;
mapfile -tu 3 times_a;
exec 3<&-;

次のコードと同じように動作します。

coproc cat;
times >&${COPROC[1]};
{  
  exec {COPROC[1]}>&-;
  mapfile -tu 3 times_a;
} 3<&${COPROC[0]};

ここから、一時ファイル記述子を完全に削除して、ソリューションに導きます。

バッシュ 4.3+:

coproc cat; times >&${COPROC[1]}; { exec {COPROC[1]}>&-; mapfile -t times_a; } <&${COPROC[0]}

バッシュ 4.0+:

coproc cat; times >&${COPROC[1]}; { eval "exec ${COPROC[1]}>&-"; mapfile -t times_a; } <&${COPROC[0]}

1ここで元のファイル記述子を閉じる必要はありません。標準入力記述子が閉じられると元のファイル記述子が閉じられるため、複製するだけでかまいません。

于 2020-07-25T07:55:21.423 に答える
1

うわー、良い質問です。

ファイルを一意に保つためにランダム性に依存しないように、mktemp を使用すると改善されます。

TMPFILE=$(mktemp aaa_XXXXXXXXXX)
times > "$TMPFILE"
mapfile -t times_a < ${tempnam}
rm "$TMPFILE"

また、mapfile の代わりに for を使用します (mapfile がないため)。

a=0; for var in $(cat "$TMPFILE"); do ((a++)); TIMES_A[$a]=$var; done

しかし、ええ、ファイルや名前付きパイプなしでそれを行う方法がわかりません。

于 2012-11-14T00:55:44.060 に答える
0

同様のことを行う1つの可能性は

times > >(other_command)

これは、プロセス置換と組み合わせた出力リダイレクトです。この方法timesは現在のシェルで実行され、出力は新しいサブプロセスにリダイレクトされます。したがってmapfile、そのコマンドは同じシェルで実行されないため、これを行うことはあまり意味がありません。おそらく、それはあなたが望むものではないでしょう. サブシェルで実行されるシェル組み込み関数への呼び出しをどちらも持つことができないため、状況は少しトリッキーです。

于 2012-11-13T23:50:28.240 に答える