4

Как можно перечислить только каталоги, у которых нет другого дочернего каталога?

Представьте себе такую структуру, как /A /A/AA /A/AB /A/AB/ABB /B /C /C/CC /C/CC/CCC /C/CC/CCC/CCCC я хотел бы использовать, чтобы find в списке только /A/AA /A/AB/ABB /B /C/CC/CCC/CCCC .

Отправной точкой будет find . -type d , но нельзя использовать ни -mindepth ни -maxdepth может ли -noleaf помочь (я не мог заставить его реагировать так, как я хотел)?

2 ответа2

4

Вот POSIX-совместимое решение, которое обрабатывает выходные данные find для удаления каталогов, которые имеют подкаталог в списке. Предполагается, что в именах каталогов нет новых строк.

{ find . -type d; echo; } |
awk 'index($0,prev"/")!=1 && NR!=1 {print prev}
     1 {sub(/\/$/,""); prev=$0}'

Объяснение: скрипт awk задерживает печать каждой строки до тех пор, пока не будет прочитана следующая строка, и печатает только предыдущую строку, если она не является префиксом. Это использует тот факт, что find списки подкаталогов сразу после своего родителя. Дополнительное "/" позволяет избежать ложного удаления foo когда foobar также существует. Нелегальный NR!=1 избегает печати начальной пустой строки и неэлегического echo; это не иметь столь же неэкспериментальный особый случай для последней строки. Вызов sub удаляет завершающий слеш из каталога верхнего уровня, если, например, был вызван find ./ .


Как обычно есть загадочный Zsh однострочник.

echo **/.(e\''test -z $REPLY/*(/DN[1])'\':h)

Более длинная, более читаемая версия:

is_leaf () { [ -z $REPLY/*(/DN[1]) ] }
echo **/.(+is_leaf:h)

Последняя строка может быть упрощена до echo **/(+is_leaf) если вы не возражаете против трейлинга / .

Краткое объяснение: в скобках указаны глобальные квалификаторы, описанные на справочной странице zshexpn . Мы фильтруем результаты glob **/ (расширяется до текущего каталога и всех его подкаталогов), оставляя только те, для которых функция is_leaf (или код между '…') возвращает 0. Код фильтра перебирает подкаталоги тестируемого соответствия ($REPLY) (фактически, [1] останавливает его после первого подкаталога) и возвращает состояние, указывающее, был ли найден хотя бы один подкаталог. Спецификатор glob / ограничивает расширение каталогами; N означает, что расширение пусто, если совпадений нет; D вызывает точечные файлы, которые будут включены; :h является модификатором истории и вызывает /. суффикс для удаления (обычно это означает dirname).

Просто чтобы проиллюстрировать возможности квалификаторов глобуса zsh, вот два других варианта (более длинный и, я думаю, более неясный) с соответствующей функцией is_leaf :

echo **/.(e\''tmp=($REPLY/*(/DN[1])); ((!#tmp))'\':h)
echo **/.(e\''$REPLY/*(/DN[1]e:REPLY=false:)'\':h)
is_leaf () { set -- $REPLY/*(/DN[1]); ((!#)); }
is_leaf () { return $REPLY/*(/DN[1]e:REPLY=1:) }
1

Это то, что я использую:

leaf () { find "${1:-.}" -depth -type d | sed  'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'; }

Назовите его, используя каталог для начала:

leaf /start/dir

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .