私は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スクリプトは元の入力順序を維持しますが、各入力ファイルの内容全体をメモリに読み込む必要があります。
答え2
GNU awkの使用MKタイム()機能:
gawk -F, '
NR==1{ print; next }
{
svn=dTime=$7
gsub(/[-:]/, " ", dTime)
dTime=mktime(dTime)
sub(/,[^,]*$/, "")
}
dTime > gId[$0] {
gId[$0]=dTime
records[$0]=svn
}
END { for(rec in records) print rec, records[rec] }' infile
バラよりgawkで事前定義された配列スキャン順序を使用する(PROCINFO["sorted_in"]
)出力中のループ巡回のための基本配列を設定します。
答え3
sort
結合するawk
#get header line
head -1 infile
#work on data
tail +2 infile | sort -t, -r -k7 | awk -F, '!seen[$1]++'
=> 7番目のフィールド(日付フィールド)、つまり最新の項目から逆順に並べ替えます。その後、最初の一意のIDを持つ行のみが印刷されます。
注:文字列には追加のカンマがあります。同じIDが同じ日付に発生すると、行は定義どおりにソートされます。日付文字列は、先行/パディング0または完全に混合された形式を使用しません。