Відповіді:
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
Наведені версії будуть плутати імена файлів, які містять нові рядки. Більш надійним рішенням є сортування за рядками, що закінчуються NUL:
find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
-Eдля MacOS.
Чому б не спробувати це:
find / -name '*f*' -printf "%h\n" | sort -u
find насправді досить рідкі - -printfоператор не вказаний. Це не працює з BSD find. Отже, не "повністю сумісний з POSIX". (Хоча sort -u в POSIX .)
По суті є два способи, які ви можете використовувати для цього. Один буде аналізувати рядок, а інший буде працювати над кожним файлом. Розбираючи рядок, використовуйте такий інструмент, як 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*
Тут ми будемо використовувати такі інструменти, find, grep, і sort.
$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/
Той самий ланцюжок інструментів, що і раніше, за винятком цього разу 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
Ось який мені здається корисним:
find . -type f -name "*somefile*" | xargs dirname | sort | uniq
Ця відповідь безсоромно ґрунтується на відповіді 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*
$ 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яку команду ви хочете використовувати.
З zsh:
typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))
printf '%s\n' $dirs
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