Удалить повторяющиеся строки с похожим префиксом
Мне нужно удалить похожие строки в файле с дублирующимся префиксом и сохранить уникальные.
Из этого,
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}'
sort -r file
: сортировка линий в обратном порядке таким образом, более длинные линии с одинаковым рисунком будут помещены перед более короткой линией того же рисунка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:^.*|::'
Пояснения:
cat -n
: нумерация всех строкsed 's:\t:|:'
: используйте '|' в качестве разделителя - вам нужно заменить его на другой, если это необходимоsort -r -t'|' -k2
: обратная сортировка с разделителем ='|' и используйте ключ 2while ... done
: похоже на решение шага 1sort -n -t'|' -k1
: сортировка в исходном порядке (нумерация)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 - количество строк.