web-dev-qa-db-ja.com

1つのパスで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,)
7
Jim

シェル文字列置換を使用して、1回grepし、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,)
_

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

13
Olorin

awkをbash readと組み合わせて使用​​できます。

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

-Fは、フィールド区切り文字としてコンマを使用するようにawkに指示します

-vは、awk変数cidをシェル変数$customer_idに設定します

行が$customer_idと一致する場合、awkは5番目と6番目のフィールドを出力し、これらには変数FIRST_NAMEおよびLAST_NAMEが割り当てられます。

名($ 5)にスペースが含まれている場合(例:a、b、c、d、Sarah Jane、Smith)-v OFS=,を追加して、フィールド間にawk出力コンマを付け、readの前にIFS=,を付けてくださいカンマで分割します。

さらに、awk'$3~cid{print..}'のような特定のフィールドのみを検索でき、IDが重要な場合は'$3~"^"cid"$"{print...}'によってそのentireフィールドと一致できます。

4
oliv

最も簡単なのは、レコード全体を変数に入れて、その上で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と一致するテキストがレコードの別の場所に偶然出現するレコードを選択する可能性があります。

4
Micheal Johnson

小さなファイル、大きなファイル。私の習慣の1つは、常にディスクを削除することですIOできる限り多くのことです。これを行う1つの方法は、ファイルを配列にプッシュすることです。もちろん、これにはenv $ IFSが適切に設定されている必要がありますファイルの場合はIOを排除します。

data=( $(cat customer-info) )

それからチェリーピックできます...

FIRST_NAME=$(echo "${data[@]}" | tr ' ' '\n' | grep "$customer_id" | cut -f5 -d,)

別の方法は、そのような配列に必要なこれらの2ビットだけを割り当てることかもしれません...

data=( $(grep "${customer_id}" customer-info | cut -d, -f5,6) )

2
jas-

既存の回答はすべてメモリ(変数)に出力を格納し、2回再生します。これは、任意に大きな入力を取り、2つのタスクを実行できる汎用ラッパーを作成する場合の問題です。代わりに、出力ストリームを複製して、2つのコマンドにストリーミングできます。

私の場合、目的は、任意に長くなる可能性がある出力ストリームのヘッダー(最初の行)と特定の(セットの)行の両方をフィルタリングすることです。簡単な例は、ディスク領域の使用状況を表示することです。

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

_df -h_を使用するコマンドに置き換え、_head -1_および_grep '/$'_をそれに適用する2つのコマンドに置き換えます。両方の出力が端末に表示されますが、前者のコマンドの出力が後者の後に表示される場合もあります。

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

  • プログラムtee "各[引数]と標準出力への[コピー]標準入力。"したがって、_command | tee /dev/stderr_を使用して、stdinの出力をstdoutとstderrの両方に送信できます。
  • command >(command2)構文はbashによって引数に置き換えられるため、_command /dev/fd/63_が実行されます。 commandが_/dev/fd/63_に書き込もうとすると、_command2_の入力(stdin)になります。これはプロセス置換と呼ばれます(_man bash_を参照)。
  • teeは引数(コマンド置換を引数として渡す)とstdoutの両方に書き込むため、別のパイプを追加して別のコマンドを実行できます。これでcommand | tee >(command2) | command3ができました。
  • 最後に、command2はstdoutに出力し、stdoutは_command3_にパイプされるため、(この例では)ヘッダー行をgrepすることになります。それは私たちが望むものではありません。それを表示したいのです。 stderrをパイピングしないので、出力をstderrにリダイレクトすることは、ターミナルに表示する簡単な方法です。つまり、_>&2_を追加して、command | tee >(command2 >&2) | command3を生成します。

問題が1つあります。出力の順序は任意です。宇宙線に応じて、上記または次のいずれかが表示される場合があります。

_$ 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 '/$'
_

しかし、待ってください、これは2番目のコマンド(grep)を壊します。これは、出力がteeからsleepにパイプされ、grepが入力を無期限に待機するためです。 。これを修正するには、サブシェルを追加します。

_$ 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はそこから読み取らない(ストリームを消費しない)ため、grepは引き続き読み取ることができます。現在は、0.01秒以内にheadが出力する限り(およびgrep側でのオーバーヘッドが少し)、確実に機能します。これは、最近のシステムではかなりの賭けであり、ユーザーが気付かないほど短い時間です。

ヘッダーとコマンドの出力の両方を取得するものを作成したかったので、これを一般化して次のようにできます。

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

関数内のteeコマンドはstdinから読み取ってstdoutに出力するだけなので、これを_df -h | grabheader | grep '/$'_として使用すると、以前の順序が狂ったコマンドと同じになります。しかし、それを順序どおりにしたいので、標準出力への送信を遅らせる必要があります。

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

catここでは、stdinに渡されたものがすべてstdoutに再び送られることを確認しています。引数を渡さず、リダイレクトを追加しないことで、それはまさにそれを行います。使用法:

_$ 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% /
_

しかし、今では、どのコマンドでもこれを行う一般的な方法があります。

0
Luc