Есть ли способ извлечь все дубликаты записей на основе определенного столбца? - PullRequest
2 голосов
/ 23 октября 2019

Я пытаюсь извлечь все (только) повторяющиеся значения из файла с разделителями трубы.

В моем файле данных содержится 800 тысяч строк с несколькими столбцами, и мне особенно интересен столбец 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

Ответы [ 4 ]

3 голосов
/ 23 октября 2019

Это может быть то, что вы хотите:

$ 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), чтобы поместиться в памяти (что не должно быть проблемой только с уникальными $ 3значения от 800 000 строк):

$ 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
1 голос
/ 23 октября 2019

Другое в 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 или что-л. Нет, поскольку разделитель изменился из пробела в трубу.

1 голос
/ 23 октября 2019

РЕДАКТИРОВАТЬ 2: В соответствии с предложением Эд сэр точно настроил мое предложение с более значимыми именами (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

Редактировать с помощьюЭд Мортон: 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 с | разграничен, поэтому немного изменил код следующим образом, что касается нового 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


Не могли бы вы, пожалуйстапопробуйте выполнить следующее, следующее даст вывод в той же последовательности, в которой строки встречаются во входном файле.

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.
0 голосов
/ 24 октября 2019

Два шага улучшения.
Первый шаг:
После

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, как указано в других, лучше, ответах.

...