扫描文本文件中是否有重复的 ID 号,并保留具有最高日期值的行,删除其他行

扫描文本文件中是否有重复的 ID 号,并保留具有最高日期值的行,删除其他行

我正在使用包含 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

结合sortawk

#get header line
head -1 infile
#work on data
tail +2 infile | sort -t, -r -k7 | awk -F, '!seen[$1]++'

=> 按第七个字段(日期字段)反向排序,即最新条目在前。然后仅打印具有第一个唯一 ID 的行。

注意事项:字符串中多余的逗号;如果相同的 ID 出现相同的日期,则该行按照反向排序的定义进行;日期字符串不使用前导/填充零或完全混合格式

相关内容