Вот 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:) }