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