Відповіді:
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