1

次のような15個のファイルがあります

file1.csv

a,cg2,0,0,0,21,0
a,cq1,10,0,0,0,0
a,cm2,0,19,0,0,0
...
a,ad10,0,0,0,37,0

file2.csv

d,cm1,0,3,0,0,0
d,cs2,0,32,0,0,0
d,cg2,0,0,9,0,0
...
d,az2,0,0,0,21,0

. . . .

file15.csv

s,sd1,0,23,0,0,0
s,cw1,0,0,7,0,0
s,c23,0,0,90,0,0
...
s,cg2,0,45,0,0,0

各ファイルの行数が異なり、15 ファイルすべての 2 番目のフィールドを比較して、15 ファイルすべての 2 番目のフィールドに共通する行を抽出したいと考えています。

この上記の場合

出力は次のとおりです。

cg2

(全15ファイルのセカンドフィールド共通の撮影です)

私はUnixとシェルスクリプトに慣れていないので、助けてください

4

2 に答える 2

3

フィールド 2 が 15 ファイルすべてに表示される 15 ファイルのそれぞれから完全な行が必要ですか? または、15 個のファイルすべてに表示されるフィールド 2 の値のリストのみが必要ですか。

前者:

a,cg2,0,0,0,21,0
d,cg2,0,0,9,0,0
. . .
s,cg2,0,45,0,0,0
. . .

後者:

cg2
. . .

後者の場合、これは機能するはずです

awk -F, '{arr[$2]++; if (FILENAME != prevfile) {c++; prevfile = FILENAME}} END {for (i in arr) {if (arr[i] == c) {print i}}}' file*.csv

複数の行に分割:

awk -F, '{
             arr[$2]++; 
             if (FILENAME != prevfile) {
                 c++; 
                 prevfile = FILENAME
             }
         }
         END {
             for (i in arr) {
                 if (arr[i] >= c) {
                     print i
                 }
             }
         }' file*.csv

説明:

  • フィールド 2 の値が発生する回数のカウントをインクリメントする
  • ファイル名が変更された場合、ファイルの数をインクリメントします (最初のファイルがヌル文字列からそのファイル名に変更され、カウントが 0 から 1 にインクリメントされます)。
  • 現在のファイル名を保存する
  • すべてのカウントが完了したら、そのキーで配列を反復します
  • 配列に含まれるカウントがファイル数以上の場合、フィールド 2 の値がすべてのファイルに表示されます (値が 1 つのファイルに複数回表示される場合は、>=代わりに をチェックすることで機能します)。==
  • キーを出力します(これはフィールド2の値です)
  • すべてのファイルを取得するためにグロブが使用されますが、それらを明示的にリストすることもできます

編集:

2 パス手法を使用して完全な一致行を印刷する方法を次に示します。上記のバージョンの修正版です。ファイルを 2 回リストするようにしてください。

awk -F, '
         FILENAME == first && flag {
             exit
         }
         ! first {
             first = FILENAME
         }
         FILENAME != first {
             flag = 1
         }
         {
             arr[$2]++; 
             if (FILENAME != prevfile) {
                 c++; 
                 prevfile = FILENAME
             }
         }
         END {
             # print the matching lines
             do {
                 if ($2 in arr) {
                     print;
                 }
             } while (getline);
             # print the list of words
             for (i in arr) {
                 if (arr[i] >= c) {
                     print i
                 }
             }
         }' file*.csv file*.csv

これは、最初のグループの最初のファイルが 2 番目のグループの最初のファイルと同じ名前であることに依存します。私が示したものと同様のグロビングを使用すると、その要件が処理されます。

一致する行を出力し (ただし、グループ化されていません)、単語のリストを出力します。どちらか一方のみが必要な場合は、不要なループ (do/whileまたはfor) をコメント アウトするか削除します。

行全体のみを印刷する場合は、出力を次のようにパイプできます。

sort -t , -k2,2

それらをグループ化します。

単語のリストのみを次のようにパイプします。

sort

比較しやすいように同じ順番に並べます。

于 2012-05-21T02:10:11.677 に答える
1

楽しい問題。

完全に Bash でそれを行う 1 つの方法は次のとおりです。

呼び出す必要があるjoin -t ',' -1 2 -2 2 file1 file2のは、2 つのファイルの 2 番目の列を結合することです。ただし、結合する前に、2 番目の列で並べ替える必要があります。

join引数として 2 つのファイルしかとらないため、for ループで連続して結合を行います。

補遺

これは、連続した結合を示す小さなトランスクリプトです。かなり簡単に適応できると思います。

$ cat 1.csv
a,b,c,d
e,f,g,h
i,j,k,l
$ cat 2.csv
7,5,4,3
3,b,s,e
2,f,5,5
$ cat 3.csv
4,5,6,7
0,0,0,0
1,b,4,4
$ join -t ',' -1 2 -2 2 1.csv 2.csv | cut -f 1 -d ',' > temp
$ cat temp
b
f
$ join -t ',' -2 2 temp 3.csv | cut -f 1 -d ','
b

最初の結合 (最初の 2 つのファイルに対する) では、結合された値が結果の最初の列に生成されます。したがって、file3、file4、file5 などに結合すると、生成する結果の最初の列が使用されるため、-2 オプションのみが必要になります。作業を非常に効率的に保つために、結合を行うたびに常に最初の列以外をすべて切り取ってください。

于 2012-05-21T01:02:43.547 に答える