我正在使用包含 7 列的多行文本文件 (.csv)。
每一行都包含“应该”是唯一的 id。还有一些日期列,其中之一是“最后修改”日期。
我发现应该是“唯一”的 id 实际上有时会重复,这是我需要通过删除除一个之外的所有 id 来解决的问题。
我在下面有一个使用 gawk 的示例,但是有没有办法使用 gawk、awk 或 grep 等来删除任何重复的行,但“最近”修改的行除外?因此,对于什么去和留有一些逻辑。
例如,此 csv 摘录有两行。除一个字段外,每个字段都是相同的。 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
假设您只想测试每个文件中的重复项,而不是跨所有文件,并且您不关心保留数据的输入顺序,那么他将使用任何版本的强制 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
但在选择行之后确实需要一秒钟才能将输入恢复到其原始顺序。
如果你真的想使用一次 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 awkMKTIME()功能:
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"]
) 设置输出时默认的数组 for 循环遍历。
答案3
结合sort
awk
#get header line
head -1 infile
#work on data
tail +2 infile | sort -t, -r -k7 | awk -F, '!seen[$1]++'
=> 按第七个字段(日期字段)反向排序,即最新条目在前。然后仅打印具有第一个唯一 ID 的行。
注意事项:字符串中多余的逗号;如果相同的 ID 出现相同的日期,则该行按照反向排序的定义进行;日期字符串不使用前导/填充零或完全混合格式