
Прелюдия:
Как найти общие пути, если введен отсортированный список путей/файлов?
Переводя на технический язык, если подавать отсортированные входные данные из 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/ample
and /exa/mple
должен быть /
not /ex
. Это предполагает, что требуется наибольший общий префикс, а не список префиксов с удаленными покрытиями. Если у вас есть /home/dave /home/dave/file1 /home/phil /home/phil/file2
and, ожидайте /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