我有一个包含 28 个字段/希瑟/属性的文件(以逗号分隔)。字段 # 使记录唯一。然而,其余字段可能是相同的。我需要找出重复者并只保留一个。如果保留第一次迭代比保留第二次更容易,我就可以了。例子:
输入文件:
1,ed23,jon,doe,director,usa
2,ed23,jon,doe,director,usa
3,er67,jake,Kogan,director,usa
4,er67,jake,Kogan,director,usa
5,dc10,Charls,Morg,manager,usa
6,kc56,patel,Kumar,associate,india
期望的输出:
2,ed23,jon,doe,director,usa
4,er67,jake,Kogan,director,usa
5,dc10,Charls,Morg,manager,usa
6,kc56,patel,Kumar,associate,india
答案1
您的示例输入很混乱 - 第一行(列标题)甚至没有字段分隔符逗号,并且大多数行在姓氏和成绩字段之间没有逗号。
为了提供稍微理智的输入,我将其编辑为如下所示:
$ cat input.txt
ID, uid ,firstname ,lastname, grade , country n28
1 , ed23 , jon , doe , director , usa
2 , ed23 , jon , doe , director , usa
3 , er67 , jake , Kogan , director , usa
4 , er67 , jake , Kogan , director , usa
5 , dc10 , Charls ,Morg , manager , usa
6 , kc56 , patel ,Kumar , associate , india
一个简单的实现,只是消除欺骗将是这样的:
$ awk -F' *, *' -v OFS=, \
'NR==1 {$1=$1;$0=$0; print; next};
{id=$1; $1=""; $0=$0; if (!seen[$0]++) {print id $0}}' input.txt
ID,uid,firstname,lastname,grade,country n28
1,ed23,jon,doe,director,usa
3,er67,jake,Kogan,director,usa
5,dc10,Charls,Morg,manager,usa
6,kc56,patel,Kumar,associate,india
这将输入字段分隔符 ( FS
) 设置为零个或多个空格,后跟一个逗号,然后是零个或多个空格,并将输出字段分隔符 ( OFS
) 设置为仅一个逗号。即它有效地从所有字段中去除前导和尾随空白。
对于第一个输入行 ( NR==1
),它使用 awk 技巧来重新格式化输入行:更改任何字段(甚至将其设置为其原始值),然后设置$0=$0
。该线路将重新格式化以使用新的 OFS。然后它打印它并跳到下一行。
对于剩余的输入,它将 $1 存储在名为 的变量中id
,将 $1 设置为空字符串,然后$0=$0
在打印 id 和该行的其余部分之前再次使用该技巧(实际上从该行中删除 $1)。
与您的示例输出不同,这会打印第一的任何重复行,而不是最后一个 - 很容易检测到您第一次看到某些内容,但很难检测到您最后一次看到它(除非您阅读了所有输入,否则您不会知道) )。此外,这不计算重复出现的次数。
要完成这两件事,需要在生成任何输出之前读取整个输入文件,并使用第二个数组 ( ids
) 来跟踪上次看到的重复项的 ID 号 - 使用两倍的内存,这对于 700K 输入可能很重要线。
$ awk -F' *, *' -v OFS=, \
'NR==1 {$1=$1;$0=$0",count";print;next};
{id=$1; $1=""; $0=$0; seen[$0]++; ids[$0]=id};
END { for (id in ids) {print ids[id] id, seen[id]} }' input.txt | \
sort -n
ID,uid,firstname,lastname,grade,country n28,count
2,ed23,jon,doe,director,usa,2
4,er67,jake,Kogan,director,usa,2
5,dc10,Charls,Morg,manager,usa,1
6,kc56,patel,Kumar,associate,india,1
sort -n
这里使用 是因为 awk 中的关联数组是无序的,所以以半随机顺序出现。 GNU awk 有一个asort()
可以按值对数组进行排序的函数,可以在ids
此处的数组上使用,但是 a)它不可移植,b)很容易将输出通过管道传输到sort -n
.
答案2
对于干净的、以逗号分隔的输入,awk
如下所示的脚本可能适合您:
awk -F, '{X=""; for (i=2;i<29;i++) X=X " " $i;} \
seen[X]!=1 {print;} \
{seen[X]=1;}' < input
第一条awk
规则通过挑选输入的 2 到 28 个“单词”来构建“键”(其中,根据参数-F,
,任何用逗号分隔的内容都是“单词”)。下一个规则将打印该行,除非已注册“密钥”,然后第三条规则会注册该行的密钥。
答案3
我假设该文件采用“简单 CSV”格式,这意味着数据中没有嵌入的逗号或嵌入的换行符。
$ tac file | awk -F , '{ key = $0; sub("[^,]*,", "", key) } !seen[key]++' | tac
2,ed23,jon,doe,director,usa
4,er67,jake,Kogan,director,usa
5,dc10,Charls,Morg,manager,usa
6,kc56,patel,Kumar,associate,india
awk
上面管道中间的代码将创建一个字符串,用作哈希中除第一行之外的所有字段的哈希中的键。它将打印第一的出现具有特定键的行并忽略所有重复项。
既然你似乎想要得到最后的tac
重复,在将输入输入到程序中之前,我使用(来自 GNU coreutils)反转输入中的行顺序awk
。然后我反转程序的输出awk
。
这种方法的缺点是计算出的键将使用与所有唯一行的组合大小减去第一个字段一样多的内存。
以下是一种更节省内存的方法,但它假设输入已排序,以便重复的行始终一起出现:
$ tac file | awk -F , '{ key = $0; sub("[^,]*,", "", key) } key != prev; { prev = key }' | tac
2,ed23,jon,doe,director,usa
4,er67,jake,Kogan,director,usa
5,dc10,Charls,Morg,manager,usa
6,kc56,patel,Kumar,associate,india
答案4
uniq
从上面的评论中详细说明- 方法:
$ tr ',' '\t' < temp/testfile | uniq -f 1 | tr '\t' ','
1,ed23,jon,doe,director,usa
3,er67,jake,Kogan,director,usa
5,dc10,Charls,Morg,manager,usa
6,kc56,patel,Kumar,associate,india
用作\t
分隔符以避免数据中出现空格。
uniq
将保留找到的第一行独特行。如果您绝对需要保留“最后”条目,则需要从文件的结尾到开头进行操作。您可以使用以下方法来做到这一点tac
:
$ tac temp/testfile|tr ',' '\t' | uniq -f 1 | tr '\t' ','|tac
2,ed23,jon,doe,director,usa
4,er67,jake,Kogan,director,usa
5,dc10,Charls,Morg,manager,usa
6,kc56,patel,Kumar,associate,india