Использование «find» для вывода списка только каталогов, не содержащих дочерних элементов

Использование «find» для вывода списка только каталогов, не содержащих дочерних элементов

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

Представьте себе структуру, которую /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помочь (я не смог заставить его реагировать так, как мне хотелось)?

решение1

Вот решение, совместимое с 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 ./был вызван eg.


Как обычно, есть загадочная однострочник zsh.

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

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

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

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

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

Чтобы проиллюстрировать возможности квалификаторов glob 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:) }

решение2

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

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

Вызовите его, используя начальный каталог:

leaf /start/dir

решение3

Чтобы найти каталоги, не имеющие подкаталогов:

find dir -type d -links 2

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

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