Отримайте список підкаталогів, що містять файл, ім'я якого містить рядок


45

Як я можу отримати список підкаталогів, що містять файл, ім'я якого відповідає конкретному шаблону?

Більш конкретно, я шукаю каталоги, які містять файл з буквою 'f' десь зустрічається у назві файлу.

В ідеалі, у списку не було б дублікатів і містився б лише шлях без імені файлу.

Відповіді:


43
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

Вище наведено всі файли нижче поточного каталогу ( .), які є звичайними файлами ( -type f) і мають fдесь своє ім'я ( -name '*f*'). Далі, sedвидаляє ім'я файлу, залишаючи лише ім'я каталогу. Потім список каталогів сортується ( sort), а дублікати видаляються ( uniq).

sedКоманда складається з однієї заміни. Він шукає відповідність до регулярного виразу /[^/]+$і замінює все, що відповідає нічому. Знак долара означає кінець рядка. [^/]+'означає один або більше символів, які не є косою рисою. Таким чином, /[^/]+$означає всі символи від фінальної косої риски до кінця рядка. Іншими словами, це відповідає імені файлу в кінці повного шляху. Таким чином, команда sed видаляє ім'я файлу, залишаючи незмінним ім'я каталогу, в якому знаходився файл.

Спрощення

Багато сучасних sortкоманд підтримують -uпрапор, який робить uniqнепотрібним. Для GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

І для MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

Крім того, якщо ваша findкоманда підтримує її, можна findбезпосередньо надрукувати імена каталогів. Це дозволяє уникнути необхідності sed:

find . -type f -name '*f*' -printf '%h\n' | sort -u

Більш надійна версія (Потрібні інструменти GNU)

Наведені версії будуть плутати імена файлів, які містять нові рядки. Більш надійним рішенням є сортування за рядками, що закінчуються NUL:

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'

У мене дуже багато файлів, що робить їх сортування занадто дорогим. Вкидання uniqв суміш дуже допомагає, видаляючи повторювані лінії, які вже знаходяться поруч. find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'. Або якщо ваші інструменти трохи старші, у uniq може не бути опції -z. find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112

1
Користувачі MacOS: Прапор sed не є -r. Чомусь його -E
David

@David Дуже правда. Відповідь оновлено для показу -Eдля MacOS.
John1024

22

Чому б не спробувати це:

find / -name '*f*' -printf "%h\n" | sort -u

Найкраща відповідь. Цілком сумісний з POSIX, на відміну від деяких відповідей вище, вище, а також отримує спеціальний приз за найкоротший трубопровід :).
км км

Мені б хотілося, щоб хтось показав терміни цього проти інших вище, тому що я відчуваю, що це набагато швидше.
dlamblin

4
@kkm Я згоден, що це найкраще рішення, але специфікації POSIX дляfind насправді досить рідкі - -printfоператор не вказаний. Це не працює з BSD find. Отже, не "повністю сумісний з POSIX". (Хоча sort -u в POSIX .)
Wildcard

8

По суті є два способи, які ви можете використовувати для цього. Один буде аналізувати рядок, а інший буде працювати над кожним файлом. Розбираючи рядок, використовуйте такий інструмент, як grep, sedабо awk, очевидно, буде швидше, але ось приклад, який показує обидва, а також, як ви можете "профілювати" два способи.

Зразок даних

Для прикладів нижче ми будемо використовувати наступні дані

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

Видаліть деякі *f*файли з dir1/*:

$ rm dir1/dir10{0..2}/*f*

Підхід №1 - Розбір по рядках

Тут ми будемо використовувати такі інструменти, find, grep, і sort.

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

Підхід №2 - Розбір за допомогою файлів

Той самий ланцюжок інструментів, що і раніше, за винятком цього разу dirnameзамість цього grep.

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

ПРИМІТКА. Наведені вище приклади використовуються head -5для обмеження кількості продукції, з якою ми маємо справу для цих прикладів. Вони, як правило, видаляються, щоб отримати повний список!

Порівняння результатів

Ми можемо використати timeдля ознайомлення з двома підходами.

dirname

real        0m0.372s
user        0m0.028s
sys         0m0.106s

греп

real        0m0.012s
user        0m0.009s
sys         0m0.007s

Тому завжди краще, якщо це можливо, мати справу з струнами.

Альтернативні методи розбору рядків

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

sed

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

awk

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u

+1 Тому що це працює, але що цікаво, це займає набагато більше часу, ніж відповідь @ John1024
Muhd

@Muhd - так, дзвінки до dirname повільні. Я працюю над альтернативою.
slm


1

Ця відповідь безсоромно ґрунтується на відповіді slm. Це був цікавий підхід, але він має обмеження, якщо імена файлів та / або директорій мали спеціальні символи (пробіл, напівколонка ...). Гарна звичка - використовувати find /somewhere -print0 | xargs -0 someprogam.

Зразок даних

Для прикладів нижче ми будемо використовувати наступні дані

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

Видаліть деякі *f*файли з dir1/*/:

rm dir1/dir\ 10{0..2}/*f*

Підхід №1 - Розбір за допомогою файлів

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

ПРИМІТКА . Наведені вище приклади використовуються head -5для обмеження кількості продукції, з якою ми маємо справу для цих прикладів. Вони, як правило, видаляються, щоб отримати повний список! також замініть, echoяку команду ви хочете використовувати.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.