Используйте 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
Объяснение:
сортировать
find
вывод в обратном порядке- теперь у вас сначала появляются подкаталоги, потом их родители
использование
awk
пропускать строки, если текущая строка является префиксом предыдущей строки- (эта команда из ответа здесь)
- теперь вы удалили "все родительские каталоги" (у вас остались родительские каталоги)
sort
их (так это выглядит нормальноfind
выход)- Вуаля! Быстро и портативно.
@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
/sort
pipe работает немного лучше, чем первоначально предложенный в этом ответе, но в значительной степени основан на нем:) Он будет работать более надежно, независимо от того, содержит ли путь специальные символы регулярного выражения или нет:
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/{}' \;