Список только дубликаты строк на основе одного столбца из файла с разделителями точкой с запятой? - PullRequest
11 голосов
/ 20 сентября 2009

У меня есть файл с кучей строк. Каждая из этих строк имеет 8 столбцов, разделенных точкой с запятой.

Как я могу (в Linux) возвращать повторяющиеся строки, но только на основе столбца № 2? Должен ли я использовать grep или что-то еще?

Ответы [ 8 ]

18 голосов
/ 20 сентября 2009

Смотрите мои комментарии в скрипте awk

$ cat data.txt 
John Thomas;jd;301
Julie Andrews;jand;109
Alex Tremble;atrem;415
John Tomas;jd;302
Alex Trebe;atrem;416

$ cat dup.awk 
BEGIN { FS = ";" }

{
    # Keep count of the fields in second column
    count[$2]++;

    # Save the line the first time we encounter a unique field
    if (count[$2] == 1)
        first[$2] = $0;

    # If we encounter the field for the second time, print the
    # previously saved line
    if (count[$2] == 2)
        print first[$2];

    # From the second time onward. always print because the field is
    # duplicated
    if (count[$2] > 1)
        print
}

Пример вывода:

$ sort -t ';' -k 2 data.txt | awk -f dup.awk

John Thomas;jd;301
John Tomas;jd;302
Alex Tremble;atrem;415
Alex Trebe;atrem;416

Вот мое решение №2:

awk -F';' '{print $2}' data.txt |sort|uniq -d|grep -F -f - data.txt

Прелесть этого решения в том, что он сохраняет порядок строк за счет совместного использования множества инструментов (awk, sort, uniq и fgrep).

Команда awk выводит второе поле, выходные данные которого затем сортируются. Затем команда uniq -d выбирает дублированные строки. На этом этапе стандартный вывод содержит список дублированных вторых полей, по одному на строку. Затем мы передаем этот список в fgrep. Флаг ' -f - ' указывает fgrep искать эти строки из стандартного ввода.

Да, вы можете сделать все возможное с командной строкой. Мне больше нравится второе решение для тренировки многих инструментов и для более ясной логики (по крайней мере, для меня). Недостатком является количество инструментов и, возможно, используемая память. Кроме того, второе решение неэффективно, поскольку оно сканирует файл данных дважды: первый раз с помощью команды awk, а второй - с помощью команды fgrep. Это соображение имеет значение только тогда, когда входной файл большой.

7 голосов
/ 20 сентября 2009

Иметь замысловатый awk скрипт.

awk 'BEGIN { FS=";" } { c[$2]++; l[$2,c[$2]]=$0 } END { for (i in c) { if (c[i] > 1) for (j = 1; j <= c[i]; j++) print l[i,j] } }' file.txt

Он работает, сохраняя счетчик всех вхождений каждого значения во втором поле и строк, имеющих это значение, затем выводит строки, счетчики которых больше 1.

Замените все экземпляры $2 на любой номер поля, который вам нужен, а file.txt в конце на ваше имя файла.

3 голосов
/ 20 сентября 2009

Как и предполагал @mjv, awk (или Perl, или Python) - лучший выбор:

awk -F';' ' {
    if (assoc[$2]) {          # This field 2 has been seen before
        if (assoc[$2] != 1) { # The first occurrence has not been printed
            print assoc[$2];  # Print first line with given $2
            assoc[$2] = 1;    # Reset array entry so we know we've printed it;
                              # a full line has 8 fields with semi-colons and
                              # cannot be confused with 1.
        }
        print $0;             # Print this duplicate entry
    }
    else {
        assoc[$2] = $0;       # Record line in associative array, indexed by
                              # second field.  
    }
}' <<!
a;b;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;2;c;d;e;f;g;h
a;z;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;4;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;x;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;4;c;d;e;f;g;h
!

Это работает, но может немного переупорядочить данные - потому что печатает первое вхождение дублированной строки при появлении второго вхождения. Пример вывода:

a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;c;c;d;e;f;g;h
a;1;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;q;c;d;e;f;g;h
a;4;c;d;e;f;g;h
a;4;c;d;e;f;g;h

Этот вариант сценария awk переупорядочивает тестирование, что приводит к несколько более компактной записи. Он также явно игнорирует искаженные строки данных, которые не содержат 8 полей, разделенных точками с запятой. Он упакован как сценарий оболочки, но без какой-либо обработки опций, поэтому вы можете предоставить только список файлов для сканирования (он считывает стандартный ввод, если в списке нет файлов). Я удалил точки с запятой в сценарии; awk не нуждается в них.

#!/bin/sh

awk -F';' '
NF == 8 {
    if (!assoc[$2]) assoc[$2] = $0
    else if (assoc[$2] != 1)
    {
        print assoc[$2]
        assoc[$2] = 1
        print $0
    }
    else print $0
}' "$@"

Кроме того, @mjv отметил, что могут быть проблемы с памятью при решении, подобном этому, если входные данные огромны, поскольку он хранит запись каждого отдельного значения поля 2 в ассоциативном массиве «assoc». Мы можем исключить, что если данные, введенные в awk, отсортированы, то, что мы можем гарантировать, используя sort, конечно. Вот вариант сценария, который работает с чудовищными входными данными (потому что sort выливает данные на диск, если необходимо хранить промежуточные результаты):

sort -t';' -k 2,2 "$@" |
awk -F';' '
BEGIN { last = ";"; line = "" }
NF == 8 {
    if ($2 != last)
    {
        last = $2
        line = $0
    }
    else if (line != "")
    {
        print line
        line = ""
        print $0
    }
    else print $0;
}'

Это сохраняет только одну строку ввода. Разумеется, выходные данные из данных приведены в отсортированном порядке.

2 голосов
/ 20 сентября 2009

Заимствование от Хай Ву :

% cat data.txt
John Thomas;jd;301
Julie Andrews;jand;109
Alex Tremble;atrem;415
John Tomas;jd;302
Alex Trebe;atrem;416

Есть действительно простой способ (с gnu-sort & gawk):
(Хотя это изменит порядок вывода!)
(Предостережение: без - стабильного сортировка может изменить порядок строк, поэтому второе вхождение предшествует первому. Следите за этим!)

cat data.txt | sort -k2,2 -t';' --stable | gawk -F';' '{if ( $2==old ) { print $0 }; old=$2; }'

Есть также способ Perl ...

cat data.txt | perl -e 'while(<>) { @data = split(/;/); if ( defined( $test{$data[1]} ) ) { print $_; } $test{$data[1]} = $_; }'

.

1 голос
/ 25 августа 2017

Простой awk подход только для удаления уникальных строк на основе столбца № 2 (или возврата дублированных строк на основе столбца № 2); Возможно, вам придется перейти на ожидаемый целевой столбец или комбинацию из нескольких столбцов $X$Y.

awk -F\; 'NR==FNR{s[$2]++;next} (s[$2]>1)' infile infile
1 голос
/ 20 сентября 2009

grep может сделать это, но я предполагаю, что вам будет намного легче с awk (иначе gawk, в некоторых системах).

Эффективная цепочка / сценарий, который будет использоваться для ваших нужд, зависит от нескольких дополнительных битов информации. Например, легко ли отсортирован входной файл, насколько велик ввод (точнее, он огромный или поток) ...

Предполагая, что отсортированный ввод (изначально или из конвейера через сортировку), скрипт awk будет выглядеть примерно так: (внимание не проверено)

Проверьте решение, предоставленное Джонатаном Леффлером или Хай Ву, для способа достижения того же без требования предварительной сортировки.

#!/usr/bin/awk
# *** Simple AWK script to output duplicate lines found in input ***
#    Assume input is sorted on fields

BEGIN {
    FS = ";";   #delimiter
    dupCtr = 0;       # number of duplicate _instances_
    dupLinesCtr = 0;  # total number of duplicate lines

    firstInSeries = 1;   #used to detect if this is first in series

    prevLine = "";
    prevCol2 = "";  # use another string in case empty field is valid
}

{
  if ($2 == prevCol2) {
    if (firstInSeries == 1) {
      firstInSeries = 0;
      dupCtr++;
      dupLinesCtr++;
      print prevLine
    }
    dupLinesCtr++;
    print $0
  }
  else
     firstInSeries = 1
  prevCol2 = $2
  prevLine = $0
}

END { #optional display of counts etc.
  print "*********"
  print "Total duplicate instances = " iHits "   Total lines = " NR;
}
0 голосов
/ 20 сентября 2009

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

Вот самый быстрый фрагмент кода, который я мог придумать в Python:

    import fileinput
    seen = dict()
    for line in fileinput.input():
        fields = line.split(';')
        key = fields[1]
        if key in seen:
            if not seen[key][0]:
                print seen[key][1],
                seen[key] = (True, seen[key][1])
            print line,
        else:
            seen[key] = (False, line)

Модуль fileinput позволяет нам обрабатывать наши строки ввода способом, аналогичным стандартной обработке awk файла / ввода ... или семантике переключателя командной строки Perl -n.

Оттуда мы просто отслеживаем первую строку, которую мы видим, с уникальным значением во втором поле и флагом, указывающим, печатали ли мы эту ранее. Когда мы сначала находим дубликат, мы печатаем первую строку с этим ключом и помечаем его как напечатанный, затем мы печатаем текущую строку. Для всех последующих дубликатов мы просто печатаем текущую строку. Очевидно, что для любого не-дупе мы просто публикуем его как запись в нашем словаре.

Вероятно, есть более элегантный способ справиться с этим логическим значением "first dupe" ... но это было для меня наиболее очевидно и не должно приводить к дополнительным накладным расходам. Создание очень простого объекта / класса с его собственным состоянием (я был напечатан) было бы вариантом. Но я думаю, что это усложнит понимание всей сути кода.

Должно быть очевидно, что это можно сделать на любом языке сценариев или программирования, который поддерживает ассоциативные массивы (хэши, словари, таблицы, как бы их ни называл ваш предпочитаемый язык). Единственное различие между этим кодом и большинством других примеров, которые я видел в этой теме, заключается в предположениях, которые я делаю относительно ваших требований (что вы бы предпочли сохранить относительный порядок строк ввода и вывода).

0 голосов
/ 20 сентября 2009

как насчет:

 sort -t ';' -k 2 test.txt | awk -F';' 'BEGIN{curr="";prev="";flag=0} \
                     NF==8{ prev=curr;
                            curr=$2;
                            if(prev!=curr){flag=1}
                            if(flag!=0 && prev==curr)flag++ ; 
                            if(flag==2)print $0}'

Я также попробовал команду uniq, которая имеет возможность отображать повторяющиеся строки "-d", но не может определить, можно ли использовать с полями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...