Linux найти-type d: как вывести список самых глубоких каталогов, например ./dir1/dir2 без ./dir1?

Linux найти-type d: как вывести список самых глубоких каталогов, например ./dir1/dir2 без ./dir1?
find . -type d

обычно будет показывать

./dir1
./dir1/dir2
./dir3
./dir3/dir4
./dir3/dir4/dir5
...

Я просто хочу

./dir1/dir2
./dir3/dir4/dir5

, без своего родителя ./dir1, ...

Другими словами, я хочу видеть только те каталоги, в которых нет подкаталогов.

Есть идеи?

EDIT: Я обнаружил, что -links 2это работает в обычной среде Linux, например,

docker run -it --rm ubuntu:bionic find /etc -type d -links 2

работает отлично.

Однако, когда я монтирую каталог (из MacOS или Windows) в контейнер Docker, все меняется, это не работает, вы можете попробовать это:

docker run -it --rm -v /etc:/etc_of_host ubuntu:bionic find /etc_of_host -type d -links 2

решение1

С zsh:

has_subdirs() () (( $# )) ${1-$REPLY}/*(ND/Y1)
print -rC1 -- **/*(ND/^+has_subdirs)

Или напрямую без посреднической функции:

print -rC1 -- **/*(ND/^e['()(($#)) $REPLY/*(ND/Y1)'])

Это:

  • fn() some-commandопределяет fnфункцию с some-commandтелом, как в оболочке Bourne и большинстве оболочек типа Bourne.
  • () { code } argsанонимная функция, которая запускается codeс argsпозиционным параметром.
  • (( $# ))здесь тело этой анонимной функции представляет собой арифметическое выражение, которое разрешается какистинныйесли $#(количество аргументов) не равно нулю.
  • ${param-default}(из оболочки Bourne) расширяется до , $paramесли параметр установлен или defaultнет. Здесь с помощью ${1-$REPLY}мы позволяем нашей функции вызываться напрямую (как has_subdirs mydir) или как подстановочной квалификаторной функции, как показано ниже.
  • $dir/*(ND/Y1)расширяется до файлов типа directory в $dir( /), включая скрытые ( Dotglob), и не выдает ошибку, если совпадений нет ( Nullglob). Но с Y1, мы останавливаемся на первом совпадении. Таким образом, анонимная функция (и, следовательно, has_subdirs) вернет true, если каталог содержит хотя бы один подкаталог.
  • print -rC1печатает свои аргументы raw на 1 Cстолбце
  • ^+has_subdirsограничивается файлами, для которых has_subdirsфункция не ( ^) возвращает значение true.

Если вам нужно использовать bashоболочку, просто сделайте следующее:

zsh << 'EOF'
  print -rC1 -- **/*(ND/^e['()(($#)) $REPLY/*(ND/Y1)'])
EOF

Или сохранить результат в массиве bash (требуется bash-4.4+):

readarray -td '' array < <(zsh << 'EOF'
  print -rNC1 -- **/*(ND/^e['()(($#)) $REPLY/*(ND/Y1)'])
EOF
)

(используя записи, разделенные NUL, чтобы иметь возможность хранить произвольные пути).

Если у вас нет zsh, но есть perl, вы можете сделать:

find . -depth -type d -print0 |
  perl -l -0ne 'print if rindex $prev, "$_/", 0; $prev = $_'

Или, если у вас GNU awk:

find . -depth -type d -print0 |
  gawk -v 'RS=\0' 'index(prev, $0"/") != 1; {prev = $0}'

Те, у кого есть findраспечатать каталогисначала глубина(выходит из ветвей, на которых они находятся) и вызывает perl/ awkдля вывода записей, которые не найдены, а затем /в начале предыдущей записи.

Опять же, чтобы сохранить файлы в массиве bash 4.4+, вам нужно будет переключиться на записи с разделителями NUL при выводе, переместив after -lили добавив in :-0perl-v 'ORS=\0'gawk

readarray -td array < <(
  find . -depth -type d -print0 |
    perl -0lne 'print if rindex $prev, "$_/", 0; $prev = $_'
)
readarray -td array < <(
  find . -depth -type d -print0 |
    gawk -v 'RS=\0' -v 'ORS=\0' 'index(prev, $0"/") != 1; {prev = $0}'
)

В некоторых системах и файловых системах (например, ext4в файловых системах на базе Linux) вы можете рассчитывать на то, что каталоги, имеющие подкаталоги, имеют количество ссылок больше 2 ( dirи dir/.дополнительную ссылку для каждого dir/subdir/..).

print -rC1 -- **/*(ND/l-3)

Или используя findв любой оболочке:

find . -type d -links -3

Это не будет работать, например, в системах btrfs(которые заменили файловые системы по умолчанию в нескольких дистрибутивах Linux), где количество ссылок на каталоги всегда равно единице, поэтому я бы не рекомендовал это как общее решение.ext4

То же самое касается объединенных файловых систем, как в вашем случае, например, overlayfsгде я нахожу некоторыеобъединеныКоличество ссылок в каталогах равно 1, независимо от того, содержат ли они подкаталоги или нет.

Однако, где бы его ни использовали, он имеет преимущество перед решениями, которые вручную подсчитывают подкаталоги, поскольку вам не нужен доступ на чтение к каталогу (нужен только доступ на поиск к его родительскому каталогу).

решение2

То, что вы ищете:

find . -type d -links 2

Каждый каталог в файловой системе имеет как минимум две жесткие ссылки — сам каталог в родительском каталоге и запись '.'. Каждая запись '..' в подкаталогах добавляет новую жесткую ссылку на каталог. Поэтому вам просто нужно найти каталоги с двумя жесткими ссылками.

Команда, предложенная в другом ответе

find . -type d -links -3

Делает то же самое, но с утверждением «каталоги, на которые имеется менее трех жестких ссылок».

решение3

Самый лучший:

p="";(find . -type d;echo) | while read d; do [[ $p && $d != $p/* ]] && echo $p; p=$d; done

В одну строку

a="";(find . -type d;echo )|while read i; do if  [[ (! $i =~ $a) && ("$a" != "") ]]; then echo $a; fi; a=$i;done

Как упомянул автор статьи:

a="";(find . -type d;echo )|while read i; do [[ $i != $a/* && ! -z "$a" ]] && echo $a; a=$i;done

решение4

Читайте man find. -mindepthВариант — то, что вам нужно.

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