Удалить повторяющиеся строки с похожим префиксом

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

Из этого,

abc/def/ghi/
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/
123/456/789/
xyz/

к этому

abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/789/
xyz/

Ценим любые предложения,

4 ответа

Решение

Быстрый и грязный способ сделать это заключается в следующем:

$ while read elem; do echo -n "$elem " ; grep $elem file| wc -l; done <file | awk '$2==1{print $1}'
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
123/456/789/
xyz/

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

Ответ в случае изменения порядка вывода.

sort -r file | awk 'a!~"^"$0{a=$0;print}'
  1. sort -r file: сортировка линий в обратном порядке таким образом, более длинные линии с одинаковым рисунком будут помещены перед более короткой линией того же рисунка

  2. awk 'a!~"^"$0{a=$0;print}': разобрать отсортированный вывод где a содержит предыдущую строку и $0 содержит текущую строку

    • a!~"^"$0 проверяет каждую строку, если текущая строка не является подстрокой в ​​начале предыдущей строки.
    • если $0 не подстрока (т.е. не похожий префикс), мы print это и сохранить новую строку в a (для сравнения со следующей строкой)

Первая строка $0 не в a потому что никакое значение не было присвоено a (первая строка всегда печатается)

Шаг 1: Это решение основано на предположении, что изменение порядка вывода разрешено. Если это так, то перед обработкой следует быстрее выполнить обратную сортировку входного файла. При обратной сортировке нам нужно сравнивать только 2 последовательные строки в каждом цикле, не нужно искать весь файл или все "известные префиксы". Я понимаю, что строка определяется как префикс и должна быть удалена, если она является префиксом любой другой строки. Вот пример удаления префиксов в файле, допускается изменение порядка:

#!/bin/bash

f=sample.txt                                 # sample data

p=''                                         # previous line = empty

sort -r "$f" | \
  while IFS= read -r s || [[ -n "$s" ]]; do  # reverse sort, then read string (line)
    [[ "$s" = "${p:0:${#s}}" ]] || \
      printf "%s\n" "$s"                     # if s is not prefix of p, then print it
    p="$s"
  done

Explainations: ${p:0:${#s}} взять первый ${#s} (длина s) символы в строке p,

Тестовое задание:

$ cat sample.txt 
abc/def/ghi/
abc/def/ghi/jkl/one/
abc/def/ghi/jkl/two/
abc/def/ghi/jkl/one/one
abc/def/ghi/jkl/two/two
123/456/
123/456/789/
xyz/

$ ./remove-prefix.sh 
xyz/
abc/def/ghi/jkl/two/two
abc/def/ghi/jkl/one/one
123/456/789/

Шаг 2: Если вам действительно нужно сохранить порядок, то этот скрипт является примером удаления всех префиксов, изменение порядка не допускается:

#!/bin/bash

f=sample.txt
p=''

cat -n "$f" | \
  sed 's:\t:|:' | \
  sort -r -t'|' -k2 | \
  while IFS='|' read -r i s || [[ -n "$s" ]]; do
    [[ "$s" = "${p:0:${#s}}" ]] || printf "%s|%s\n" "$i" "$s"
    p="$s"
  done | \
  sort -n -t'|' -k1 | \
  sed 's:^.*|::'

Пояснения:

  1. cat -n: нумерация всех строк
  2. sed 's:\t:|:': используйте '|' в качестве разделителя - вам нужно заменить его на другой, если это необходимо
  3. sort -r -t'|' -k2: обратная сортировка с разделителем ='|' и используйте ключ 2
  4. while ... done: похоже на решение шага 1
  5. sort -n -t'|' -k1: сортировка в исходном порядке (нумерация)
  6. sed 's:^.*|::': убрать нумерацию

Тестовое задание:

$ ./remove-prefix.sh 
abc/def/ghi/jkl/one/one
abc/def/ghi/jkl/two/two
123/456/789/
xyz/

Примечания. В обоих решениях самые дорогостоящие операции - это вызовы sort, Решение в шаге 1 вызывает sort один раз, и решение в шаге 2 вызывает sort дважды. Все остальные операции (cat, sed, whileСтрока сравнения,...) не на том же уровне стоимости.

В решении шага 2 cat + sed + while + sed "эквивалентен" сканированию этого файла 4 раза (что теоретически может выполняться параллельно из-за конвейера).

Следующий awk делает то, что запрашивается, он читает файл дважды.

  • На первом проходе он создает все возможные префиксы в строке
  • Во втором проходе проверяется, является ли строка возможным префиксом, если не печатается.

Код является:

awk -F'/' '(NR==FNR){s="";for(i=1;i<=NF-2;i++){s=s$i"/";a[s]};next}
           {if (! ($0 in a) ) {print $0}}' <file> <file>

Вы также можете сделать это, прочитав файл один раз, но затем сохраните его в памяти:

awk -F'/' '{s="";for(i=1;i<=NF-2;i++){s=s$i"/";a[s]}; b[NR]=$0; next}
           END {for(i=1;i<=NR;i++){if (! (b[i] in a) ) {print $0}}}' <file>

Аналогично решению Аллана, но с использованием grep -c:

while read line; do (( $(grep -c $line <file>) == 1 )) && echo $line;  done < <file>

Учтите, что эта конструкция читает файл (N+1) раз, где N - количество строк.

Другие вопросы по тегам