どのように2回grepできますか?

どのように2回grepできますか?

grepファイルで2回実行せずに変数を1回だけ埋める方法はありますか?ファイルが小さくてあまり問題ではありませんが、一度に完了できるかどうかを知りたかったです。

FIRST_NAME=$(grep "$customer_id" customer-info|cut -f5 -d,)
LAST_NAME=$(grep "$customer_id" customer-info|cut -f6 -d,)

答え1

シェル文字列置換grepを1回使用して2回分割できます。

NAME=$(grep "$customer_id" customer-info | cut -f5,6 -d,)
FIRST_NAME=${NAME%,*}
LAST_NAME=${NAME#*,}

またはbashの場合は、プロセス置換を使用します。

IFS=, read FIRST_NAME LAST_NAME < <(grep "$customer_id" customer-info | cut -f5,6 -d,)

read入力を分割しIFS、最初の値をに割り当て、FIRST_NAME残りの値をに割り当てますLAST_NAME。プロセス置換とリダイレクトを使用すると、サブシェルを使用せずに出力を渡すことができます< <(...)grep ... | cut ...read

答え2

最も簡単な方法は、レコード全体を変数に入れて使用するcutことです。

RECORD=$(grep "$customer_id" customer-info)
FIRST_NAME=$(echo "$RECORD"|cut -f5 -d,)
LAST_NAME=$(echo "$RECORD"|cut -f6 -d,)

また、個人的にはより具体的な正規表現を使用することをお勧めします。顧客IDが常に行の先頭にある場合は、一致が行の先頭にあることを要求するgrep '^'"$customer_id"代わりに書き込むことができます。grep "$customer_id"それ以外の場合は、顧客 ID に一致するテキストがレコードの他の場所に表示されるレコードを選択できます。

答え3

awkBashで使用できますread

read -r FIRST_NAME LAST_NAME <<< $(awk -F, -v cid="$customer_id" '$0~cid{print $5,$6}' customer-info)

-Fフィールド区切り文字でカンマを使用するように awk に指示する

-vawk変数をcidシェル変数に設定$customer_id

行が一致すると、$customer_idawkは5番目と6番目のフィールドを印刷し、このフィールドには変数FIRST_NAMEsumが割り当てられますLAST_NAME

名前($ 5)にスペース(たとえば、a、b、c、d、Sarah Jane、Smith)が含まれている場合は、フィールド間に-v OFS=,出力コンマを追加し、awkプレフィックスreadとしてwithを追加IFS=,してコンマで区切るようにします。

また -awkなどの特定のフィールド内でのみ検索が可能で、'$3~cid{print..}'そのフィールドと一致します。みんな'$3~"^"cid"$"{print...}'あなたのIDにとって重要な場合はフィールドです。

答え4

既存の答えはすべて、出力をメモリ(変数)に保存して2回再生します。ランダムに大きな入力を受け入れ、これに対して2つのことができる一般的なラッパーを作成しようとすると、これは問題になります。代わりに、出力ストリームをコピーして両方のコマンドにストリーミングできます。

私の場合、目的は出力ストリームから任意に長くできるヘッダー(最初の行)と特定の行セットをフィルタリングすることです。簡単な例は、ディスク容量の使用量を表示することです。

$ df -h | tee >(head -1 >&2) | grep '/$'
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

df -h使用したいコマンドに置き換えて、適用したい2つのコマンドにhead -1置き換えます。grep '/$'両方の出力が端末に表示されますが、電子コマンドの出力は後者の後に表示されます。

どのように動作しますか?

  • プログラムはtee「各[引数]に標準入力をコピーし、標準出力にもコピーします。」したがって、を使用してstdinの出力をstdoutおよびstderrに送信できますcommand | tee /dev/stderr
  • このcommand >(command2)構文は bash の引数に置き換えられるのでcommand /dev/fd/63実行されます。commandを書き込もうとすると、/dev/fd/63の入力(stdin)で終了しますcommand2。これをプロセス置換と呼びます(参考資料を参照man bash)。
  • tee引数(コマンド置換を引数として渡す)と標準出力が同時に作成されるため、異なるパイプを追加して別のコマンドを実行できます。今私たちはそれを持っていますcommand | tee >(command2) | command3
  • 最後に、 command2 は stdout として出力し、stdout は にパイプされるのでcommand3(私の例では) ヘッダラインを grep します。それは私たちが望むものではありません。私たちはそれを見せたいです。 stderrをパイプしないので、出力をstderrにリダイレクトすることが端末に表示される簡単な方法です。つまり、 を追加すると>&2結果は ですcommand | tee >(command2 >&2) | command3

問題があります。出力順序は任意です。宇宙船によっては、上または次を見ることができます。

$ df -h | tee >(head -1 >&2) | grep '/$'
/dev/sda1     202G  145G   57G  72% /
Filesystem    Size  Used Avail Use% Mounted on

この問題を解決する時代遅れですが、信頼できる方法は、(過度に設計された、または時代遅れの方法ではない)2番目のコマンドに次の短いスリープモードを追加することです。

$ df -h | tee >(head -1 >&2) | sleep 1; grep '/$'

grepしかし、ちょっと待ってください。これでtee、出力がからパイプに入り、sleep入力grepを無期限に待機するため、2番目のコマンド()が中断されます。この問題を解決するためにサブシェルを追加します。

$ df -h | tee >(head -1 >&2) | (sleep 0.01; grep '/$')
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

出力はサブシェルではなくサブシェルgrepにリダイレクトされます。sleepここでは読まないので(ストリームを消費しない)、読み続けることができますgrephead0.01秒(grepのわずかなオーバーヘッドを含む)以内に出力する限り、安定して動作します。これは最新のシステムで公正な賭けであり、ユーザーが気付かないほど短いです。

一部のコマンドのヘッダーと出力の両方を使用するものを作成したいので、次のように一般化できます。

function grabheader {
    tee >(head -1 >&2)
}

関数のコマンドはteestdinからのみ読み取ってstdoutに出力するのでdf -h | grabheader | grep '/$'

function grabheader {
    tee >(head -1 >&2) | (sleep 0.01; cat)
}

catこれにより、標準入力に渡されたすべての項目が標準出力に戻ります。パラメータを渡さずにリダイレクトを追加しないことでこれを行います。使用法:

$ df -h | grabheader | grep '/$'
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

もちろん、特別な場合にはdfこれをより簡単に行うことができます。

$ df -h /
Filesystem    Size  Used Avail Use% Mounted on
/dev/sda1     202G  145G   57G  72% /

しかし今、私たちはどんなコマンドでもこれを行うための一般的な方法を持っています。

関連情報