Как найти общие пути из списка путей/файлов

Как найти общие пути из списка путей/файлов

Прелюдия:

Как найти общие пути, если введен отсортированный список путей/файлов?

Переводя на технический язык, если подавать отсортированные входные данные из stdin, как выбрать самый короткий правильный префикс из stdin?

Здесь "префикс" имеет обычное значение, например, строка 'abcde' имеет префикс 'abc'. Вот мой пример ввода

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2'
/home/dave
/home/dave/file1
/home/dave/sub2/file2

Это пример дляудалить последовательный правильный префиксиз stdin, используя команду sed:

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2' | sed "N; /^\(.*\)\n\1\//D; P; D" 
/home/dave/file1
/home/dave/sub2/file2

Вопрос:

Мой вопрос в том, каксохранить правильный префиксвместо этого, и удалите все строки, которые имеют этот префикс. Синус /home/dave/file1и и /home/dave/sub2/file2имеют префикс /home/dave, /home/daveбудет сохранен, а два других — нет. Т.е. это сделает полную противоположность тому, что sedделает команда выше.

Больше информации:

  • Входные данные уже будут отсортированы.
  • Если бы у меня было /home/dave /home/dave/file1 /home/phil /home/phil/file2( echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2\n/home/phil\n/home/phil/file2'), я бы ожидал , что ответ будет « /home/daveи» ./home/phil

Приложение:

У меня есть два дисковых тома, содержащих схожее содержимое. Я хочу скопировать то, что есть в v1, но отсутствует в v2, в другой дисковый том, v3. Используя find, sort, и comm, я могу получить список того, что копировать, но мне нужно дополнительно очистить этот список. То есть, пока у меня есть /home/daveв списке, мне не нужны два других.

Спасибо!

решение1

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

Пример:

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2\n/home/phil\n/home/phil/file1' | removecoverings 
/home/phil
/home/dave

Код команды removecoverings:

#!/usr/bin/env python2

import sys

def list_startswith(a, b):
    if not len(a) >= len(b):
        return False
    return all(x == y for x,y in zip(a[:len(b)],b))

def removecoverings(it):
    g = list(it)
    g.sort(key=lambda v: len(v.split('/')), reverse=True)
    o = []
    while g:
        c = g.pop()
        d = []
        for v in g:
            if list_startswith(v.split('/'), c.split('/')):
                d.append(v)
        for v in d:
            g.remove(v)
        o.append(c)
    return o

for o in removecoverings(l.strip() for l in sys.stdin.readlines()):
    print o

В этом ответе используется Python. Он также делает компонентный, а не строковый общий префикс. Лучше для путей, так как общий префикс /ex/ampleand /exa/mpleдолжен быть /not /ex. Это предполагает, что требуется наибольший общий префикс, а не список префиксов с удаленными покрытиями. Если у вас есть /home/dave /home/dave/file1 /home/phil /home/phil/file2and, ожидайте /home/dave /home/philвместо /home. Это не тот ответ, который вы искали.

Пример:

$ echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2' | commonprefix 
/home/dave

Код команды commonprefix:

#!/usr/bin/env python2

import sys

def commonprefix(l):
    # this unlike the os.path.commonprefix version
    # always returns path prefixes as it compares
    # path component wise
    cp = []
    ls = [p.split('/') for p in l]
    ml = min( len(p) for p in ls )

    for i in range(ml):

        s = set( p[i] for p in ls )         
        if len(s) != 1:
            break

        cp.append(s.pop())

    return '/'.join(cp)

print commonprefix(l.strip() for l in sys.stdin.readlines())

решение2

Учитывая, что входные данные отсортированы, псевдокод будет следующим:

$seen = last_line;
if current_line begins exactly as $seen then next
else { output current_line; $seen = current_line }

Перевод на язык Perl (да, Perl, самый красивый язык сценариев из всех):

perl -e '
my $l = "\n";
while (<>) {
    if ($_ !~ /^\Q$l/) {
        print;
        chomp;
        $l = $_;
    }
}
'

Кредит:Бен Бакарис @bsb.me.uk, из comp.lang.perl.misc. Спасибо, Бен, работает отлично!

решение3

И, однострочная версия ответа xpt. Опять же, предполагая сортированный ввод:

perl -lne 'BEGIN { $l="\n"; }; if ($_ !~ /^\Q$l/) { print $_; $l = $_; }'

Выполнить на примере входных данных

/home/dave
/home/dave/file1
/home/dave/sub2/file2
/home/phil
/home/phil/file2 

с использованием

echo -e '/home/dave\n/home/dave/file1\n/home/dave/sub2/file2\n/home/phil\n/home/phil/file2' | perl -lne 'BEGIN { $l="\n"; }; if ($_ !~ /^\Q$l/) { print $_; $l = $_; }'

дает

/home/dave
/home/phil

Вся магия кроется в аргументах командной строки perl: -eпозволяет нам задать скрипт в командной строке, -nвыполняет итерацию по строкам файла (помещая каждую строку в $_) и -lобрабатывает для нас переносы строк.

Скрипт работает, используя lдля отслеживания последнего увиденного префикса. BEGINБлок запускается до чтения первой строки и инициализирует переменную в строку, которая не будет видна (без новых строк). Условие запускается для каждой строки файла (удерживается ) $_. Условие выполняется для всех строк файла и говорит: «если строка не имеет текущего значения в lкачестве префикса, то вывести строку и сохранить ее как значение l». Из-за аргументов командной строки это по сути идентично другому скрипту.

Проблема в том, что оба скрипта предполагают, что общий префикс существует как отдельная строка, поэтому не ищите общий префикс для ввода типа

/home/dave/file1
/home/dave/file2

Связанный контент