Використовуючи “find”, перераховуйте лише каталоги, у яких немає більше дитини


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 допомога (я не міг змусити його реагувати так, як я хотів)?


1
Gilles

Відповіді:


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 ./ називався.


Як завжди, є cryptic zsh один лайнер.

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

Більш тривала версія:

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

Останній рядок можна спростити до echo **/(+is_leaf) якщо ви не маєте на увазі відставання /.

Резюме пояснення: речі в дужках є Глобальні класи , задокументовані в zshexpn сторінка людини. Ми фільтруємо результати глобу **/ (розширюється до поточного каталогу і всіх його підкаталогів), зберігаючи тільки ті, для яких функція is_leaf (або код між '…' ) повертає 0. Глобус коду фільтра в підкаталогах перевіряється відповідності ( $REPLY ) (насправді, [1] припиняє роботу після першого підкаталогу) і повертає статус, що вказує, чи було знайдено принаймні один підкаталог. Кваліфікатор глоба / обмежує розширення до каталогів; 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:) }

Схоже, я повинен почати працювати з zsh ... У всякому разі, я думаю, що скрипт awk більш читабельний, ніж сценарій sed. Але весь find / awk розривається, якщо шлях відрізняється від "." вказано для пошуку. У цьому випадку ім'я шляху буде повторюватися як перший вивід, якщо під цим контуром існують будь-які залишки. Приклад: mkdir -p A/AA; { find A/ -type d; echo; } | awk 'index($0,prev"/")!=1 && NR!=1 {print prev} 1 {prev=$0}' буде виводити A/ так само, як A/AA. Я не можу протестувати zsh ще ...
MaoPU

@MaoPU: проблема у вашому прикладі - це трейлінг /; код працював би find A або find /A. Я вирішив свою відповідь. Існує дуже незвичайний випадок, що не є правильним (якщо ви запускаєте код у файловій системі без каталогу, відмінного від кореня, ви отримуєте порожній рядок замість рядка з / ). Змініть awk print Відповідь, якщо ви дбаєте про цю помилку. Я також знайшов трохи більш просту версію zsh (це майже читається зараз).
Gilles

Велике пояснення. Я дізнався, що bash-4.x також підтримує ** глобус, так що може бути спосіб написати один з цих акуратних вкладишів у bash. Оскільки оригінальне запитання запропонувало використання find і awk скрипт досить читабельний, я пішов в основному для цього рішення і додав leaf () { { find -- "${1:-.}" -type d; echo; } | awk 'index($0,prev"/")!=1 && NR!=1 {print prev} 1 {sub(/\/$/,""); prev=$0}'; } моєму .bashrc. Дякую.
MaoPU

@MaoPU: bash 4 **, так що вам не потрібно find, але вам все одно потрібен інший спосіб фільтрації не-листових каталогів. Можна використовувати a for цикл **/., але це насправді не єдиний матеріал.
Gilles

1

Це я використовую:

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

Зателефонуйте йому, використовуючи каталог, щоб почати з:

leaf /start/dir

Я не міг анонсувати сценарій sed, який використовує інші оператори (ніж s ), тим більше, що простір утримання тут особливо доречний. (Зауважте, що крапка з комою не є POSIX і може бути замінена новими рядками за межами Linux.) Але я не міг підвищити заміну замінюваної змінної! (І досі залишається проблема аргументу, з якого починається ім'я -, який є загальним find роздратування.)
Gilles

@Gilles: Яка проблема інших операторів, ніж s (просто жартую, правда?) Тільки щоб зрозуміти це: Яка проблема непрямої заміни змінних? Жоден з моїх тестів не порушив сценарій. У будь-якому випадку, чи є рішення для імен, починаючи з - (не бачу загального find вирішення цього у вашій відповіді ні)?
MaoPU

@MaoPU: Для імен, що починаються з - (наприклад, називається каталог -print ), Я думаю, що якщо у вас є єдиний шлях і вірите в виконавця, скрупульозно слідуючи версії POSIX 21 століття, find -- "${1:-.}" досить добре. В іншому випадку ви можете попередньо обробити аргумент для попередньої підготовки ./ якщо починається з -.
Gilles

@MaoPU: Для чого потрібні лапки, спробуйте викликати каталог * (без лапок, оболонка буде розширювати шаблон) або foo bar (без лапок, оболонка розділила б аргумент на слова).
Gilles

@Gilles: Так, загалом в цьому знаю, але в цьому випадку я не зміг відтворити помилку з контуром, що містить пробіл. Також я намагався експериментувати з find -- зупинити його читання аргументів, але це не спрацювало. Думаю, для того, щоб це було доказом, необхідно перевірити відносні або абсолютні траєкторії, а також a ./ у випадку, якщо це відносний шлях.
MaoPU
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.