特定のcsv列から2つの単語をgrepし、数を数えます。

特定のcsv列から2つの単語をgrepし、数を数えます。

次のgrep / awkクエリを完了するためのより良い方法を見つけようとしています。以下は問題の簡単な例です。

私は正規表現を使ってこれを達成しました。

grep -Po ^(?:[^,]+,\s?){7}(Want|Need) | awk -F ',' 'NR>=2{print $8}' | sort | uniq -c

私のCSVファイルは次のとおりです。

1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
1896,Ranger,2021,State,Postcode,Surname,Industry,Selling,Turbo,Good
1896,Ranger,2021,State,Postcode,Surname,Industry,Need,Turbo,Good

上記のジョブはgrepを使用して行全体を印刷します。

1896,Ranger,2021,State,Postcode,Surname,Industry,Want
1896,Ranger,2021,State,Postcode,Surname,Industry,Need

その後、列8の値を計算できます。私の質問は、正規表現を使用して選択したグループのみを返すようにgrep / regexクエリを作成する方法です。

たとえば、

Want
Need

この記事を書いた理由は、純粋にここで正規表現を使用するより良い方法を理解するためです。私はこれを行う他の方法があることを知っています。

答え1

PCREアサーションを探しているようです\K。 ~からフェレ:

\K(Perl 5.10.0以降で利用可能)という特別な形式のこの構成があり、これは正規表現エンジンが$&で囲まれず、\K以前に一致する項目を「保持」するようにします。

だから

$ grep -Po '^(?:[^,]+,\s?){7}\K(Want|Need)' file.csv
Want
Need

より一般的には、この種の操作は次のように行われます。後ろを見て主張 - しかし、Perlは可変長の振り返りをサポートせず、grep -Pもサポートしません。

$ grep -Po '^(?<=(?:[^,]+,\s?){7})(Want|Need)' file.csv
grep: lookbehind assertion is not fixed length

また、見ることができます前方と後方の長さがゼロのアサーション

答え2

これは、一致を実行するためにlibpcre(perl正規表現のスタンドアロン実装)を使用する-PGNU実装の非標準(オプションで長く実験的に考えられている)オプションです。grep

libpcreこれは完全な実装に進み、いくつかのGNU / Linuxディストリビューションの独自のパッケージにありますが、サンプルgrepコード()で独自のコマンドが提供されます。pcregrepgrep

pcregrepgrepそのキャプチャグループを出力するためにオプションの数値引数を使用するように、GNUの非-o標準オプションを拡張しました。

だからここにあります:

pcregrep -o1 '^(?:[^,]+,\s?){7}(Want|Need)'

あるいは、GNUを持たないシステムgrep(またはgrepPCREをサポートせずにGNUを構築したシステム)でも機能できるという利点を持つ実際のシステムを使用することもできますpcregrep

perl -lne 'print $1 if /^(?:[^,]+,\s?){7}(Want|Need)/'

ただし、perlデフォルトでは、入力はGNUと同様に、ロケールのテキストエンコーディングに従ってデコードされませんgrep。この特別な場合、一致するテキストは移植可能な文字セットの文字のみを使用します。これは、入力がロケールとは異なるエンコードされていてもまだ機能するため、非常に有利です。

perl入力のテキストをロケールエンコーディングに従ってデコード(および出力からエンコード)するには、を追加します-Mopen=locale


しかし、あなたの場合、Perl正規表現を使用する価値はありません。ここで使用するすべてのPerl演算子には、標準のERE演算子と同等のものがあります(代替以外のBREも同様です)。

  • (?:...):単にperl / ERE(...)またはBREであり、\(...\)キャプチャはありません。
  • +:EREでも同じ、\{1,\}BREでも同じ
  • ?:EREと同じ、\{0,1\}EREでは
  • {7}:EREでも同じ、\{7\}BREでも同じ
  • (Want|Need):EREと同じです(交互の方向を選択したときの動作は少し異なりますが)。
  • \s[[:space:]]BREとEREから
  • ^[^,]:BREまたはEREで同じ

sedはパターンの一致部分を抽出するツールです(一方、grepafteredコマンドは正規表現に一致する行をg/re/p印刷します)。p標準はBREを使用しますが、ほとんどの実装はEREへの移行をサポートします(これは標準の次のバージョンに追加される予定です)。resedsed-E

したがって、ここではperl上記のコマンドと同じように移植可能な操作も実行できます。

LC_ALL=C sed -nE 's/^([^,]+,[[:space:]]?){7}(Want|Need).*$/\2/p'

または-E

LC_ALL=C sed -n 's/^\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\(Want\).*$/\2/p; t
                 s/^\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\(Need\).*$/\2/p'

または別のものに置き換えてくださいWantNeed

LC_ALL=C sed -E 's/^(([^,]+,[[:space:]]?){7})(Want|Need)/\1Desire/'
LC_ALL=C sed 's/^\(\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\)Want/\1Desire/; t
              s/^\(\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\)Need/\1Desire/

1それ以来、他の実装ではast-openのように常にlibpcreを使用するのではなく、同様の正規表現を使用する-P独自のオプションを追加しました(探索の主張はサポートされていますがサポートされていません)。perlgrep\K

答え3

すでにawkを使用しているので、ここでは必要ありませんgrep。あなたはそれを必要とせず、sort必要uniq -cもありません。たとえば、

$ awk -v search=Want -F, '$8 ~ search { count[$8]++ };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
    1   Want

$ awk -v search='Want|Need' -F, '$8 ~ search { count[$8]++ };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
    1   Want
    1   Need

または、一致する行も印刷するには、次の手順を実行します。

$ awk -v search='Want|Need' -F, '$8 ~ search { count[$8]++ ; print };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
1896,Ranger,2021,State,Postcode,Surname,Industry,Need,Turbo,Good
    1   Want
    1   Need

-v IGNORECASE=1コマンドラインに追加して、GNU awkに大文字と小文字を区別するか、必要に応じて正確な一致などの高度な機能を追加することもできます。

$ awk -v search='want' -v exact=1 -v IGNORECASE=1 -F, '
    BEGIN {if (exact == 1) search = "^(" search ")$"};
    $8 ~ search { count[$8]++ ; print };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
    1   Want

以下は、antにある間、Wantフィールド 8 とまったく一致しないため、出力を生成しません。

$ awk -v search='ant' -v exact=1 -v IGNORECASE=1 -F, '
    BEGIN {if (exact == 1) search = "^(" search ")$"};
    $8 ~ search { count[$8]++ ; print };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 

注:コマンドラインオプションの処理を実行するより良い方法は明らかです(例:選択項目のインポート関数またはシェルスクリプトラッパーを作成してsh / bash組み込み機能を使用しますgetoptが、-vawkのオプションを使用してスクリプトの外側でawkに変数を設定することは、このような単純な操作に簡単で便利です。

ところで、awkは変数をスクリプト自体の後ろのコマンドラインに追加し、使用せずに変数を割り当てることもできます-v(awkは型のすべての引数をx=y変数xを値yに設定すると解釈します。残念ながらこれを使用する難しいです。彼らの中に=– おそらく不可能かもしれません。

ただし、を使用する場合とは異なり、-vこれらの変数はいいえこれはBEGIN {}声明で確認できます。たとえば、ant以下を設定しても、次は一致しますexact=1

$ awk -F, 'BEGIN {if (exact == 1) search = "^(" search ")$"};
           $8 ~ search { count[$8]++ ; print };
           END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' \
    search=ant IGNORECASE=1 exact=1 input.csv 
1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
    1   Want

GNU awkのマニュアルページから:

コマンドラインのファイル名が次の形式の場合は、var=val 変数の割り当てとして扱われます。変数にvar 値が割り当てられますval。 (時々こんな場合があります。後ろにBEGIN実行されたルールはありません。 )

コマンドライン変数の割り当ては、AWKが入力がフィールドとレコードに分類される方法を制御するために使用する変数に値を動的に割り当てるのに最も便利です。単一のデータファイルに複数回渡す必要がある場合は、状態を制御するのにも役立ちます。

IMOはこれを以前のawkスクリプトと互換性のある従来の機能として扱います-v

-v var=val
--assign var=val

プログラムが実行される前に、変数に値が割り当てられますvalvarこれらの変数値はいBEGINAWKプログラムで使用可能なルールです。

(上記の引用の中の「以後」と「だ」は私が太字で強調したものです)

答え4

0検索中の文字列の1つが入力に表示されない場合は、まったく印刷せずに数を印刷するのを見たい場合は、強力で持ち運び可能で効率的で簡潔な方法は次のとおりです。これ:

$ awk -F',' -v tgts='Want,Need' '
    { cnt[$8]++ }
    END { split(tgts,t); for (i in t) print t[i], cnt[t[i]]+0 }
' file
Want 1
Need 1

したがって、ここで正規表現がどこに適用されるのかを把握することは困難です。たぶん次のようなものがあります。

$ awk -F',' -v tgts='Want|Need' '
    $8 ~ ("^"tgts"$") { cnt[$8]++ }
    END { split(tgts,t,/[|]/); for (i in t) print t[i], cnt[t[i]]+0 }
' file
Want 1
Need 1

または:

$ awk -F',' -v tgts='Want|Need' '
    $0 ~ ("([^,]*,){7}"tgts"(,|$)") { cnt[$8]++ }
    END { split(tgts,t,/[|]/); for (i in t) print t[i], cnt[t[i]]+0 }
' file
Want 1
Need 1

しかし、正規表現はスクリプトを複雑にし、より脆弱にするだけです(検索したい文字列に、または.同じ正規表現メタ文字が含まれている場合は正規表現を持つスクリプトは失敗しますが、最初のスクリプトは引き続き機能*します)。しません。$8あなたの入力には数十億の固有値があります。

関連情報