【问题标题】:Is there way to extract all the duplicate records based on a particular column?有没有办法根据特定列提取所有重复记录?
【发布时间】:2020-02-19 22:48:15
【问题描述】:

我正在尝试从管道分隔文件中提取所有(仅)重复值。

我的数据文件有 80 万行和多列,我对第 3 列特别感兴趣。所以我需要获取第 3 列的重复值并从该文件中提取所有重复的行。

不过,我可以做到这一点,如下所示..

cat Report.txt | awk -F'|' '{print $3}' | sort | uniq -d >dup.txt

我将上面的循环如下所示..

while read dup
do
   grep "$dup" Report.txt >>only_dup.txt
done <dup.txt

我也试过awk方法

while read dup
do
awk -v a=$dup '$3 == a { print $0 }' Report.txt>>only_dup.txt
done <dup.txt

但是,由于文件中有大量记录,因此需要很长时间才能完成。所以我正在寻找一种简单快捷的替代方案。

例如,我有这样的数据:

1|learning|Unix|Business|Requirements
2|learning|Unix|Business|Team
3|learning|Linux|Business|Requirements
4|learning|Unix|Business|Team
5|learning|Linux|Business|Requirements
6|learning|Unix|Business|Team
7|learning|Windows|Business|Requirements
8|learning|Mac|Business|Requirements

而我的预期输出不包括唯一记录:

1|learning|Unix|Business|Requirements
2|learning|Unix|Business|Team
4|learning|Unix|Business|Team
6|learning|Unix|Business|Team
3|learning|Linux|Business|Requirements
5|learning|Linux|Business|Requirements

【问题讨论】:

标签: unix awk ksh


【解决方案1】:

这可能是你想要的:

$ awk -F'|' 'NR==FNR{cnt[$3]++; next} cnt[$3]>1' file file
1|learning|Unix|Business|Requirements
2|learning|Unix|Business|Team
3|learning|Linux|Business|Requirements
4|learning|Unix|Business|Team
5|learning|Linux|Business|Requirements
6|learning|Unix|Business|Team

或者如果文件对于所有键($3 值)来说太大而无法放入内存(这不应该是 800,000 行中唯一的 $3 值的问题):

$ cat tst.awk
BEGIN { FS="|" }
{ currKey = $3 }
currKey == prevKey {
    if ( !prevPrinted++ ) {
        print prevRec
    }
    print
    next
}
{
    prevKey = currKey
    prevRec = $0
    prevPrinted = 0
}

$ sort -t'|' -k3,3 file | awk -f tst.awk
3|learning|Linux|Business|Requirements
5|learning|Linux|Business|Requirements
1|learning|Unix|Business|Requirements
2|learning|Unix|Business|Team
4|learning|Unix|Business|Team
6|learning|Unix|Business|Team

【讨论】:

  • 哈哈,第一个。 :D 可能就够了,你。
  • @JamesBrown 是的,这应该是他们所需要的,但我开始认为文件很大,因为我不知道 lac 是什么,所以我首先想出了上面的第二个脚本 :-)!
【解决方案2】:

EDIT2:根据 Ed sir 的建议,用更有意义的数组名称 (IMO) 微调了我的建议。

awk '
match($0,/[^\|]*\|/){
  val=substr($0,RSTART+RLENGTH)
  if(!unique_check_count[val]++){
    numbered_indexed_array[++count]=val
  }
  actual_valued_array[val]=(actual_valued_array[val]?actual_valued_array[val] ORS:"")$0
  line_count_array[val]++
}
END{
  for(i=1;i<=count;i++){
    if(line_count_array[numbered_indexed_array[i]]>1){
      print actual_valued_array[numbered_indexed_array[i]]
    }
  }
}
'  Input_file

由 Ed Morton 编辑:FWIW 这是我在上述代码中命名变量的方式:

awk '
match($0,/[^\|]*\|/) {
  key = substr($0,RSTART+RLENGTH)
  if ( !numRecs[key]++ ) {
    keys[++numKeys] = key
  }
  key2recs[key] = (key in key2recs ? key2recs[key] ORS : "") $0
}
END {
  for ( keyNr=1; keyNr<=numKeys; keyNr++ ) {
    key = keys[keyNr]
    if ( numRecs[key]>1 ) {
      print key2recs[key]
    }
  }
}
' Input_file


编辑: 由于 OP 将 Input_file 更改为 |delimited,因此将代码更改为如下所示,它处理新的 Input_file(感谢 Ed Morton 先生的指点出来)。

awk '
match($0,/[^\|]*\|/){
  val=substr($0,RSTART+RLENGTH)
  if(!a[val]++){
    b[++count]=val
  }
  c[val]=(c[val]?c[val] ORS:"")$0
  d[val]++
}
END{
  for(i=1;i<=count;i++){
    if(d[b[i]]>1){
      print c[b[i]]
    }
  }
}
'   Input_file


您能否尝试以下操作,以下将按照 Input_file 中出现的行的相同顺序给出输出。

awk '
match($0,/[^ ]* /){
  val=substr($0,RSTART+RLENGTH)
  if(!a[val]++){
    b[++count]=val
  }
  c[val]=(c[val]?c[val] ORS:"")$0
  d[val]++
}
END{
  for(i=1;i<=count;i++){
    if(d[b[i]]>1){
      print c[b[i]]
    }
  }
}
'  Input_file

输出如下。

2 learning Unix Business Team
4 learning Unix Business Team
6 learning Unix Business Team
3 learning Linux Business Requirements
5 learning Linux Business Requirements

以上代码说明:

awk '                                 ##Starting awk program here.
match($0,/[^ ]* /){                   ##Using match function of awk which matches regex till first space is coming.
  val=substr($0,RSTART+RLENGTH)       ##Creating variable val whose value is sub-string is from starting point of RSTART+RLENGTH value to till end of line.
  if(!a[val]++){                      ##Checking condition if value of array a with index val is NULL then go further and increase its index too.
    b[++count]=val                    ##Creating array b whose index is increment value of variable count and value is val variable.
  }                                   ##Closing BLOCK for if condition of array a here.
  c[val]=(c[val]?c[val] ORS:"")$0     ##Creating array named c whose index is variable val and value is $0 along with keep concatenating its own value each time it comes here.
  d[val]++                            ##Creating array named d whose index is variable val and its value is keep increasing with 1 each time cursor comes here.
}                                     ##Closing BLOCK for match here.
END{                                  ##Starting END BLOCK section for this awk program here.
  for(i=1;i<=count;i++){              ##Starting for loop from i=1 to till value of count here.
    if(d[b[i]]>1){                    ##Checking if value of array d with index b[i] is greater than 1 then go inside block.
      print c[b[i]]                   ##Printing value of array c whose index is b[i].
    }
  }
}
'  Input_file                         ##Mentioning Input_file name here.

【讨论】:

  • 如果您想出比 a、b、c 和 d 更有意义的名称来为您的数组命名以使代码更易于理解,那么如果您仍然需要我,我会查看它。
  • 在命名变量时,根据它们的用途来命名它们,而不是它们的实现方式。例如numbered_indexed_array - 它告诉我给定的数组是由数字索引的,但绝对没有关于它是如何被使用的。只需看一眼代码numbered_indexed_array[++count]=val,我就可以看出它是按数字索引的,所以这个名字没有用。考虑一下该数组包含什么,然后根据它命名它会很有用。
  • 您有一个名为val 的变量,它告诉我这是一个值。好的 - 一切都是有价值的,所以没有用。它的价值是什么?看起来它是用于确定唯一性的关键值,因此您可以将其命名为 key,这将是一个比 val 更有用的名称。现在!unique_check_count[val]++ 怎么样?看起来像!seen[key]++ 这样会更有用。现在numbered_indexed_array[++count]=val 怎么样?好吧count 没用,算什么?看起来它是唯一键值的计数,因此整行将是keys[++numKeys]=key
  • 看起来actual_valued_array[val] 应该类似于key2recs[key],因为它似乎将键映射到相关记录。等等……
  • 希望您不介意,但我编辑了您的答案,以使用恕我直言添加脚本版本更有用、更有意义的变量名称来演示我的意思。在考虑数组的用途时,我意识到 unique_check_count[val]++line_count_array[val]++ 相同,因此我可以摆脱其中一个 - 这是提出有意义的名称的一个附带好处,它可以帮助您确定改进的方法代码。还有其他改进的机会(例如,考虑三元组与上面的if 相比)
【解决方案3】:

awk 中的另一个:

$ awk -F\| '{                  # set delimiter
    n=$1                       # store number
    sub(/^[^|]*/,"",$0)        # remove number from string
    if($0 in a) {              # if $0 in a
        if(a[$0]==1)           # if $0 seen the second time
            print b[$0] $0     # print first instance
        print n $0             # also print current
    }
    a[$0]++                    # increase match count for $0
    b[$0]=n                    # number stored to b and only needed once
}' file

样本数据的输出:

2|learning|Unix|Business|Team
4|learning|Unix|Business|Team
3|learning|Linux|Business|Requirements
5|learning|Linux|Business|Requirements
6|learning|Unix|Business|Team

还有,这样行吗:

$ sort -k 2 file | uniq -D -f 1

-k2,5 或smth。不,因为分隔符从空格变为管道。

【讨论】:

  • 所以分隔符从空格变成了管道。也许稍后修复,得跑。
  • 修复了更改的分隔符。
  • 酷。在 Ed sir 向我提到之后,我也固定了分隔符,就像我要求你检查我的代码一样,我也要求他 :)
  • @RavinderSingh13 你的变量名太长了。我自己就是一个 b、c、d 人。 :D :D :D
【解决方案4】:

两步改进。
第一步:
之后

awk -F'|' '{print $3}' Report.txt | sort | uniq -d >dup.txt
# or
cut -d "|" -f3 < Report.txt | sort | uniq -d >dup.txt

你可以使用

grep -f <(sed 's/.*/^.*|.*|&|.*|/' dup.txt) Report.txt
# or without process substitution
sed 's/.*/^.*|.*|&|.*|/' dup.txt > dup.sed
grep -f dup.sed Report.txt

第二步:
使用其他更好的答案中给出的awk

【讨论】:

    猜你喜欢
    • 2019-09-04
    • 2021-05-23
    • 2021-12-03
    • 2013-09-20
    • 1970-01-01
    • 1970-01-01
    • 2019-09-28
    • 2022-12-29
    • 1970-01-01
    相关资源
    最近更新 更多