重複したID番号があるかどうかをテキストファイルをスキャンし、日付値が最も高い行を維持し、他の行を削​​除します。

重複したID番号があるかどうかをテキストファイルをスキャンし、日付値が最も高い行を維持し、他の行を削​​除します。

私は7つの列を含む複数行のテキストファイル(.csv)で作業しています。

各行には、固有でなければならないIDが含まれています。いくつかの日付列もあり、そのうちの1つが「最後の修正」日付です。

「固有」でなければならないIDが実際に重複する場合があることを発見しました。これは、1つを除くすべてのIDを削除して解決する必要がある問題です。

以下にgawkを使用する例を示しますが、gawk、awk、grepなどを使用して、「最も最近」修正された行を除く重複行を削除する方法はありますか?したがって、何が行き、何が残るのかについての論理があります。

たとえば、このcsv抜粋には2つの行があります。 1つの分野を除いて、すべての分野が同じです。 ID番号が「同じ」というのは私にとって「重複」という意味です。

両方の行完全同じですが。

csvファイルの最後の(7番目)フィールドの日付のため、ある項目が他の項目よりも古いです。

ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-08-26 17:32:00
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-09-11 22:15:00

ファイルに対してgawk、cat、grep、cut、awkなどの操作を実行できますか?

a) 重複した ID を持つコンテンツを識別します。 b) 最後のフィールドに「最新」の日付がある重複項目のみを保持します。

理想的には、データベースに供給されるCSVヘッダーを含む最初の行を維持する必要があります。

これがほぼうまく機能する理由です。

gawk -i inplace '!a[$0]++' *.csv

実際には、重複エントリを削除して1行だけ残すように見えますが、最終フィールドで最も古い日付値に基づいて何を保持するかを決定するロジックはありません。

助けることができます...

ベストアンサー1

すべてのファイルではなく、各ファイル内でのみ重複エントリをテストし、データの入力順序を維持することに興味がないと仮定すると、彼は目的のタスクを実行するためにforce POSIXツールのすべてのバージョンを使用します。すべてのUnixシステムで動作します。

$ cat tst.sh
#!/usr/bin/env bash

tmp=$(mktemp) || exit 1
sep=','
for file in "$@"; do
    {
        head -n 1 "$file" &&
        tail -n 2 "$file" |
            sort -t "$sep" -r -k 7,7 |
            awk -F "$sep" '$1 != prev { print; prev=$1 }'
    } > "$tmp" &&
    mv -- "$tmp" "$file"
done

たとえば、

$ cat file
foo,bar
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-08-26 17:32:00
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-09-11 22:15:00

$ ./tst.sh file*

$ cat file
foo,bar
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-09-11 22:15:00

sort上記のツールだけがすべての入力を一度に処理する必要があり、他のツールは一度に1行だけ処理し、要求sortページングなどを使用して大容量ファイルを処理するように設計されています。入力ファイルが非常に大きいです。

入力行の順序を本当に維持するには、上記の内容を変更して適用できます。DSU慣用語これを行うことができる必要があります。

$ cat tst.sh
#!/usr/bin/env bash

tmp=$(mktemp) || exit 1
sep=','
for file in "$@"; do
    awk -v OFS="$sep" '{ print (NR>1), NR, $0 }' "$file" |
        sort -t "$sep" -k1,1 -k9,9r |
        awk -F "$sep" 'NR==1{print; next} $1 != prev{ print; prev=$1 }' |
        sort -t "$sep" -k1,1 -k2,2n |
        cut -d "$sep" -f3- \
    > "$tmp" &&
    mv -- "$tmp" "$file"
done

sortただし、行を選択した後、入力を元の順序に復元するのに1秒かかります。

入力順序を維持しながら、1回のGNU awk呼び出しですべての操作を実行するには、次の手順を実行します。

$ cat tst.awk
BEGIN { FS="," }
FNR == 1 {
    delete id2maxTs
    delete id2fnr
    delete fnr2input
    print
    next
}
{ id=$1; ts=$7 }
!(id in id2maxTs) || (ts > id2maxTs[id]) {
    if ( id in id2fnr ) {
        prevFnr = id2fnr[id]
        delete fnr2input[prevFnr]
    }
    id2maxTs[id]   = ts
    id2fnr[id]     = FNR
    fnr2input[FNR] = $0
}
ENDFILE {
    for ( i=1; i<=FNR; i++ ) {
        if ( i in fnr2input ) {
            print fnr2input[i]
        }
    }
}

$ gawk -i inplace -f tst.awk file*

$ cat file
foo,bar
ID12345,Here is some text,ABCDEFG,7,9,2022-08-18 20:15:00,2022-08-26 17:32:00

gawkスクリプトは元の入力順序を維持しますが、各入力ファイルの内容全体をメモリに読み込む必要があります。

おすすめ記事