Используйте GNU find, чтобы показать только листовые каталоги

Я пытаюсь использовать GNU find для поиска только каталогов, которые не содержат других каталогов, но могут содержать или не содержать обычные файлы.

Мое лучшее предположение до сих пор было:

find dir -type d \( -not -exec ls -dA ';' \)

но это просто дает мне длинный список "."

Спасибо!

9 ответов

Решение

Вы можете использовать -links, если ваша файловая система совместима с POSIX (т. Е. Каталог содержит ссылку для каждого подкаталога в нем, ссылку от его родителя и ссылку на себя, таким образом, счетчик из 2 ссылок, если у него нет подкаталогов).

Следующая команда должна делать то, что вы хотите:

find dir -type d -links 2

Однако, похоже, он не работает на Mac OS X (как упомянул @Piotr). Вот еще одна версия, которая медленнее, но работает на Mac OS X. Она основана на его версии с исправлением для обработки пробелов в именах каталогов:

find . -type d -exec sh -c '(ls -p "{}"|grep />/dev/null)||echo "{}"' \;

Я только что нашел другое решение, которое работает как на Linux и MacOS (без find -exec)!

Это включает в себя sort (дважды) и awk:

find dir -type d | sort -r | awk 'a!~"^"$0{a=$0;print}' | sort

Объяснение:

  1. сортировать find вывод в обратном порядке

    • теперь у вас сначала появляются подкаталоги, потом их родители
  2. использование awk пропускать строки, если текущая строка является префиксом предыдущей строки

    • (эта команда из ответа здесь)
    • теперь вы удалили "все родительские каталоги" (у вас остались родительские каталоги)
  3. sort их (так это выглядит нормально find выход)
  4. Вуаля! Быстро и портативно.

@Sylvian решение не работает для меня на Mac OS X по какой-то непонятной причине. Так что я придумал немного более прямое решение. Надеюсь, это поможет кому-то:

find . -type d  -print0 | xargs -0 -IXXX sh -c '(ls -p XXX | grep / >/dev/null) || echo XXX' ;

Объяснение:

  • ls -p заканчивает каталоги знаком '/'
  • так (ls -p XXX | grep / >/dev/null) возвращает 0, если нет каталогов
  • -print0 && -0 это заставить Xargs обрабатывать пробелы в именах каталогов

У меня есть несколько странно названных файлов в моих деревьях каталогов, которые сбивают с толку awk как в ответе @AhmetAlpBalkan. Поэтому я выбрал немного другой подход

  p=;
  while read c;
    do 
      l=${#c};
      f=${p:0:$l};
      if [ "$f" != "$c" ]; then 
        echo $c; 
      fi;
      p=$c; 
    done < <(find . -type d | sort -r) 

Как в awk Решение, я перевернуть сортировку. Таким образом, если путь к каталогу - это подпуть предыдущего попадания, вы можете легко это различить.

Вот p мой предыдущий матч, c текущее совпадение, l длина текущего совпадения, f это первый l совпадающие символы предыдущего матча. только я echo те хиты, которые не соответствуют началу предыдущего матча.

Проблема с awk предлагаемое решение состоит в том, что сопоставление начала строки кажется запутанным, если имя пути содержит такие вещи, как + в названии некоторых из подкаталогов. Это вызвало awk вернуть ряд ложных срабатываний для меня.

Этот awk/sortpipe работает немного лучше, чем первоначально предложенный в этом ответе, но в значительной степени основан на нем:) Он будет работать более надежно, независимо от того, содержит ли путь специальные символы регулярного выражения или нет:

find . -type d | sort -r | awk 'index(a,$0)!=1{a=$0;print}' | sort

Помни это awk строки индексируются 1, а не 0, что может показаться странным, если вы привыкли работать с языками на основе C.

Если индекс текущей строки в предыдущей строке равен 1 (т.е. она начинается с нее), мы пропускаем ее, что работает так же, как совпадение "^"$0.

Существует альтернатива сыромятной коже (rh), которая намного проще в использовании.

Для файловых систем, отличных от btrfs:

      rh 'd && nlink == 2'

Для бтрфс:

      rh 'd && "[ `rh -red %S | wc -l` = 0 ]".sh'

Более короткая/быстрая версия для btrfs:

      rh 'd && "[ -z \"`rh -red %S`\" ]".sh'

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

Для версии, которая максимально эффективно работает на всех файловых системах:

      rh 'd && (nlink == 2 || nlink == 1 && "[ -z \"`rh -red %S`\" ]".sh)'

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

Сыромятная кожа (rh) доступна на https://raf.org/rawhide или https://github.com/raforg/rawhide. Он работает как минимум на Linux, FreeBSD, OpenBSD, NetBSD, Solaris, macOS и Cygwin.

Отказ от ответственности: я являюсь текущим автором сыромятной кожи.

Мои 2 цента по этой проблеме:

      (
for listed_file in $(find . -type f)
do
    listed_file_name=$(basename "$listed_file")
    listed_directory=${listed_file/$listed_file_name/}
    echo "$listed_directory"
done
) | sort | uniq

Он использует подоболочку для захвата выходных данных выполнения и расширения параметров оболочки для получения имени каждого каталога, а затем сортирует и фильтрует уникальные значения. Поиск только файлов гарантирует, что для каждого файла не будут указаны промежуточные каталоги.

Что насчет этого? Это портативное устройство, и оно не зависит от количества ссылок. Обратите внимание, что важно поставить root/folder без запаздывания.

find root/folder -type d | awk '{ if (length($0)<length(prev) || substr($0,1,length(prev))!=prev) print prev; prev=($0 "/") } END { print prev }'

Вот решение, которое работает на Linux и OS X:

find . -type d -execdir bash -c '[ "$(find {} -mindepth 1 -type d)" ] || echo $PWD/{}' \; 

или же:

find . -type d -execdir sh -c 'test -z "$(find "{}" -mindepth 1 -type d)" && echo $PWD/{}' \;
Другие вопросы по тегам