同じ値を持つレコードを30個以上削除する

同じ値を持つレコードを30個以上削除する

大きなCSVがあり、インスタンスが30を超える場合は、名前フィールド($ 8)、中間名フィールド($ 9)、および姓フィールド($ 10)が同じレコードを削除したいと思います。

TYPE|10007|44|Not Available||||CHRISTINE||HEINICKE|||49588|2014-09-15|34
TYPE|1009|44|Not Available||||ELIZABETH||SELIGMAN|||34688|2006-02-12|69
TYPE|102004|44|Not Available||||JANET||OCHS|||11988|2014-09-15|1022
TYPE|1000005|44|Not Available||||KIMBERLY||YOUNG|||1988|2016-10-04|1082

これが私が今まで持っているものです:

awk -F"|" '++seen[tolower($8 || $9 || $10)] <= 30' foo.csv > newFoo.csv

答え1

私は、「単純なCSV」ファイル、つまり埋め込み区切り文字または埋め込み改行文字を含むフィールドを含まないファイルを扱っていると仮定します。

30項目を確認した後に表示される項目インスタンスを削除します。

awk -F '|' 'count[$8,$9,$10]++ < 30' file 

また、これらのエントリの最初の30個のインスタンスを削除します。上記の方法を使用して計算し、フィルタリングと出力のためにファイルを再解析できます。

awk -F '|' '!output { ++count[$8,$9,$10]; next } count[$8,$9,$10] <= 30' file output=1 file

パラメータリストでファイルを2回参照し、変数を中間値outputに設定しました。1その後、コードは「カウントモード」(コードの最初のブロック)から「フィルタと出力モード」(最初のブロック以降のテスト)に切り替わります。

使用しているキーを小文字に変更する必要がある場合は、読みやすくするために最初に別々に計算することをお勧めします。

awk -F '|' '
    { key = tolower($8) SUBSEP tolower($9) SUBSEP tolower($10) }
    count[key]++ < 30' file 
awk -F '|' '
    { key = tolower($8) SUBSEP tolower($9) SUBSEP tolower($10) }
    !output { ++count[key]; next }
    count[key] <= 30' file output=1 file

値は、この回答の最初の2つのコードスニペットと同じように、カンマで区切られたキーとして使用されるときに値の間に挿入されるSUBSEP特別な区切り文字です。awk

答え2

これは、入力がファイルに保存され、パイプから出ていないと仮定し、入力ファイルを2回読み取る方法です。

awk -F'|' '
    { name = tolower($8 FS $9 FS $10) }
    NR==FNR { nameCnts[name]++; next }
    nameCnts[name] <= 30
' file file

または、パイプ入力とファイルで作業できるように、ファイルの内容を配列に保存します。

awk -F'|' '
    {
        name = tolower($8 FS $9 FS $10)
        nameCnts[name]++
        recs[NR] = $0
        names[NR] = name
    }
    END {
        for ( recNr=1; recNr<=NR; recNr++ ) {
            name = names[recNr]
            if ( nameCnts[name] <= 30 ) {
                print recs[recNr]
            }
         }
    }
' file

あるいは、パイプ入力またはファイルで作業することができ、大容量ファイルに最も効率的である可能性があります。

awk '
    BEGIN { FS=OFS="|" }
    { print tolower($8 FS $9 FS $10), $0 }
' file |
sort -t'|' -k1,3 |
awk -F '|' '
    {
        name = $1 FS $2 FS $3
        sub(/([^|]*\|){3}/,"")
    }
    name != prev {
        if ( cnt <= 30 ) printf "%s", buf
        buf = ""
        cnt = 0
        prev = name
    }
    {
        buf = buf $0 ORS
        cnt++
    }
    END  { if ( cnt <= 30 ) printf "%s", buf }
'

最後のスクリプトは入力順序を保持しません。必要に応じて、元の行番号の追加のソートキーを追加して、最終的に元の順序で並べ替えることができます。

上記はすべてawkで動作し、GNU awkではより簡単に実装できますが、IMHOの場合、移植性を失う価値はありません。

関連情報