Удалить пути из списка, если родительский каталог также есть в списке

Кажется, что github.com ведет себя невежливо, заявляя, что ресурс не существует, когда делается неаутентифицированный запрос GET -. Обычно wgetсначала пытается запросить ресурс без аутентификации и повторяет попытку с этими данными аутентификации, когда выдается ошибка 401 Not Authenticated.

Это можно обойти, передав --auth-no-challenge, чтобы wgetотправлял данные аутентификации в первый раз.

4
20.04.2021, 18:57
6 ответов

Краткое awk решение:

<infile sort -u |awk 'NR==1 || index($0, pre"/")!=1{print; pre=$0}'
6
28.04.2021, 22:51

Я предполагаю, что список путей не может быть отсортирован, и результирующий список путей должен быть в том же порядке, что и во входных данных. Я также предполагаю, что путь не содержит встроенных символов новой строки.

Использование/bin/sh:

#!/bin/sh

set --
while IFS= read -r pathname; do
        for p do
                case $pathname in ("$p"/*) continue 2 ;; esac
        done

        set -- "$@" "$pathname"
done <list

printf '%s\n' "$@"

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

Список допустимых путей хранится в позиционных параметрах.

Оболочка bash, очевидно, сможет запустить приведенный выше скрипт, но если вы хотите что-то написанное специально для этой оболочки, вы можете сказать

#!/bin/bash

accepted=()
while IFS= read -r pathname; do
        for p in "${accepted[@]}"; do
                [[ $pathname == "$p"/* ]] && continue 2
        done

        accepted+=("$pathname")
done <list

printf '%s\n' "${accepted[@]}"

Использование awkс тем же подходом, что и выше:

$ awk '{ for (i=1; i<=n; ++i) if (index($0, accepted[i] "/") == 1) next; accepted[++n]=$0 } END { for (i=1; i<=n; ++i) print accepted[i] }' list
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p

Код awk, никифицированный:

{
        for (i = 1; i <= n; ++i)
                if (index($0, accepted[i] "/") == 1)
                        next

        accepted[++n] = $0
}

END {
        for (i = 1; i <= n; ++i)
                print accepted[i]
}

Вы должны увидеть очевидное сходство между этой awkпрограммой и вариациями шелл-кода в самом начале.

Используется index()для проверки того, является ли принятый путь префиксом к текущему пути. Вместо этого вы могли бы использовать if ($0 ~ "^" acceped[i] "/"), но недостатком этого является то, что сами пути используются как часть регулярного выражения.Это начинает иметь значение, когда у вас есть пути, содержащие такие символы, как .и *и т. д.

9
28.04.2021, 22:51

С perl (и подстановкой процесса сортировки, чтобы гарантировать сортировку ввода):

$ perl -lne '
    unless (defined($paths) && m:^($paths)/:) {
      $paths{$_}++;
      $paths=join("|", map +( "\Q$_\E" ), keys %paths);
    };

    END { print join("\n", sort keys %paths) }' <(sort input.txt)
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p

Он постепенно строит регулярное выражение, соответствующее путям, которые он уже видел. Если путь ранее не встречался, он добавляет его к хэшу %paths, который используется как для построения регулярного выражения, так и для содержания списка путей, которые необходимо вывести в конце скрипта.

Каждый путь окружен \Q и \E при добавлении к регулярному выражению, чтобы убедиться, что любые perlreметасимволы -(, такие как ., ?, *и т. д. )отключены.

5
28.04.2021, 22:51

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

(*Под нормализованным я подразумеваю /foo/barили /foo/bar/, а не, например. /foo/asdf/../barили /foo///bar//. Вывод из findне будет проблемой, поскольку, хотя он и дает ненормализованный -вывод, если задан ненормализованный -начальный каталог, вывод, по крайней мере, непротиворечив.)

Путь может по-прежнему быть префиксом другого, будучи только родственным, но не родительским, например. /fooи /foobar. Чтобы иметь дело с такими случаями, мы можем добавить завершающую косую черту к каждой строке, в которой ее еще нет.

Следовательно, (с добавлением /fooи /foobarк тесту и отсутствие попытки игры в гольф с кодом):

$ sort paths.txt | awk '! /\/$/ { $0 = $0 "/" } 
                        last && last == substr($0, 1, length(last)) { next; } 
                        { last = $0; sub(/\/$/, "", $0); print }' 
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
/foo
/foobar

Первая строка добавляет косую черту к текущей строке $0, если необходимо; второй сравнивает строку с последней сохраненной строкой (в last), если она есть, и отбрасывает совпадающие строки; третий хранит и печатает все неотброшенные строки с удаленной косой чертой. (Удалите sub(...), чтобы сохранить их.)

8
28.04.2021, 22:51

GNU sed с расширенным режимом регулярных выражений -E. Предыдущая строка без подмножеств сохраняется в резервном пространстве.

< file sort \
| sed -En '
    G
    /^([^\n]+)\/.*\n\1$/d
    s/\n.*//p;h
'

< file sort \
| perl -lne '
    $prev //= $_;
    print($prev = $_)
       if index($_, "$prev/");
'

POSIX sed не позволяет [^\n], поэтому мы переписываем конструкции, совместимые с POSIX

< file sort \
| sed -e '
    H;x
    \|^\(..*\)\n\1/|{
      s/\n.*//;h;d
    }
    g
'
2
28.04.2021, 22:51

Для списка файлов изfindвы можете использоватьfind... -pruneчтобы избежать проблемы в первую очередь :исключить нисхождение к дочерним элементам текущего пути, если это сам каталог. В этом Q&A показан пример, и см. справочную страницу. Если вы пытаетесь совместить его с -oпо ИЛИ некоторых других условий, вам может понадобиться find... \( -name 'foo*' -o -name 'bar*' \) -prune.

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

напр. если вы используете find -name marker.txt -printf '%h\n'для поиска каталогов, которые содержат marker.txt, вы не можете использовать -pruneдля этого, потому что -nameсоответствует только файлу в каталоге, а не самому каталогу.

Или для других будущих читателей, чей список файлов не является прямо из find.

2
28.04.2021, 22:51

Теги

Похожие вопросы