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