Удалить каждую строку, если найден случай - PullRequest
0 голосов
/ 30 августа 2018

У меня есть файл с таким форматом содержимого:

1  6  8
1  6  9
1  12 20
1  6
2  8
2  9
2  12
2  20
2  35

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

Я должен иметь это в качестве вывода:

2 35

Я пытался использовать:

awk '{for(i=2;i<=NF;i++){if($i in a){next};a[$i]}} 1' 

но, похоже, это не работает.

Что не так?

Ответы [ 5 ]

0 голосов
/ 05 сентября 2018

Это может сработать для вас (GNU sed):

sed -r 'H;s/^[0-9]+ +//;G;s/\n(.*\n)/\1/;h;$!d;s/^([^\n]*)\n(.*)/\2\n  \1/;:a;/^[0-9]+ +([0-9]+)\n(.*\n)*[^\n]*\1[^\n]*\1[^\n]*$/bb;/^[0-9]+ +[0-9]+ +([0-9]+)\n(.*\n)*[^\n]*\1[^\n]*\1[^\n]*$/bb;/\n/P;:b;s/^[^\n]*\n//;ta;d' file

Это не серьезное решение, однако оно демонстрирует, чего можно достичь, используя только сопоставление и подстановку.

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

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

0 голосов
/ 30 августа 2018
$ cat tst.awk
NR==FNR {
    cnt[$2]++
    cnt[$3]++
    next
}
cnt[$2]<2 && cnt[$NF]<2

$ awk -f tst.awk file file
2  35
0 голосов
/ 30 августа 2018

Однопроходный awk, который хэширует все записи в r[NR] и сохраняет другой массив a[$i] для значений, видимых в полях $2,...NF.

awk ' {
    for(i=2;i<=NF;i++)       # iterate fields starting from the second
        if($i in a) {        # if field value was seen before
            delete r[a[$i]]  # delete related record
            a[$i]=""         # clear a
            f=1              # flag up
        } else {             # if it was not seen before
            a[$i]=NR         # add record number to a
            r[NR]=$0
        }
    if(f!=1)                 # if flag was not raised
        r[NR]=$0             # store record on record number
    else                     # if it was raised
        f=""                 # flag down
}
END {
    for(i=1;i<=NR;++i)
        if(i in r)
            print r[i]       # output remaining
}' file

Выход:

2  35
0 голосов
/ 30 августа 2018

Самый простой способ - алгоритм двойного прохода, при котором вы дважды читаете файл.

Идея состоит в том, чтобы сохранить все значения в массиве a и посчитать, сколько раз они появляются. Если значение появляется 2 или более раз, это означает, что вы нашли более одной записи и не должны печатать строку.

awk '(NR==FNR){a[$2]++; if(NF>2) a[$3]++; next} 
     (NF==2) && (a[$2]==1);
     (NF==3) && (a[$2]==1 && a[$3]==1)' <file> <file>

На практике вам следует избегать таких вещей, как a[var]==1, если вы не уверены, находится ли var в массиве, так как он создаст этот элемент массива. Однако, так как мы никогда больше не увеличиваем его, можно продолжать.

Если вы хотите достичь того же самого с более чем тремя полями, вы можете сделать:

awk '(NR==FNR){for(i=2;i<=NF;++i) a[$i]++; next }
     {for(i=2;i<=NF;++i) if(a[$i]>1) next }
     {print}' <file> <file>

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

awk '{for(i=2;i<=NF;++i) a[$i]++; b[NR]=$0}
     END{ for(j=1;j<=NR;++j) {
            $0=b[j];
            for(i=2;i<=NF;++i) if(a[$i]>1) continue
            print $0
          }
         }' <file>

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

awk '{ for(i=2;i<=NF;++i) if ($i in a) delete b[a[$i]]; else { a[$i]=NR; b[NR]=$0 }}
     END { for(n=1;n<=NR;++n) if(n in b) print b[n] }' <file>

примечание: вы никогда не должны стремиться к самому короткому решению, кроме самого читаемого!

0 голосов
/ 30 августа 2018

Не могли бы вы попробовать следующее.

awk '
FNR==NR{
  for(i=2;i<=NF;i++){
    a[$i]++
  }
  next
}
(NF==2 && a[$2]==1) || (NF==3 && a[$2]==1 && a[$3]==1)
'  Input_file  Input_file

Вывод будет следующим.

2  35
...