Получите список подкаталогов, которые содержат файл, имя которого содержит строку

Как дополнение к другим хорошим ответам, Вы можете настройки крона проверки работоспособности с тестовыми страницами онлайн:

46
01.04.2018, 09:59
8 ответов
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

Вышеупомянутое находит все файлы ниже текущего каталога (.) это - регулярные файлы (-type f) и имейте f где-нибудь на их имя (-name '*f*').Далее, sed удаляет имя файла, оставляя просто имя каталога. Затем список каталогов отсортирован (sort) и удаленные дубликаты (uniq).

sed команда состоит из единственной замены. Это ищет соответствия к регулярному выражению /[^/]+$ и замены что-либо соответствующее этому ничему. Знак доллара означает конец строки. [^/]+' средства один или несколько символов, которые не являются наклонными чертами. Таким образом, /[^/]+$ средства все символы от заключительной наклонной черты в конец строки. Другими словами, это соответствует имени файла в конце полного пути. Таким образом команда sed удаляет имя файла, оставляя название без изменений каталога, в котором был файл.

Упрощения

Многие современные sort команды поддерживают a -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

Больше устойчивой версии (Инструменты Requires GNU)

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

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
44
27.01.2020, 19:34
  • 1
    у меня есть много файлов, которое делает сортировку их слишком дорогостоящей. Бросок 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 30.06.2017, 21:06
  • 2
    MacOS: флаг sed не является-r. По некоторым причинам его-E –  David 01.04.2018, 06:50
  • 3
    @David. Ответ, обновленный для показа -E для MacOS. –  John1024 01.04.2018, 08:17

Существует по существу 2 метода, которые можно использовать, чтобы сделать это. Каждый проанализирует строку, в то время как другой будет воздействовать на каждый файл. Парсинг строки использует инструмент такой как grep, sed, или awk очевидно, будет быстрее, но здесь пример, показывающий обоим, а также как можно "представить" эти 2 метода.

Демонстрационные данные

Для примеров ниже мы будем использовать следующие данные

$ 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 смотреть на 2 подхода.

dirname

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

grep

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
8
27.01.2020, 19:34
  • 1
    +1, поскольку это работает, но интересно это берет много раз дольше, чем ответ @John1024 –  Muhd 01.02.2014, 04:49
  • 2
    @Muhd - да вызовы к dirname, является медленным. Я работаю над альтернативой. –  slm♦ 01.02.2014, 04:50

Почему бы не попробовать это:

find / -name '*f*' -printf "%h\n" | sort -u
23
27.01.2020, 19:34

Этот ответ беззастенчиво основан на ответе шлма. Это был интересный подход, но он имеет ограничение, если имена файлов и/или каталогов имеют специальные символы (пробел, полуколонок...). Хорошей привычкой является использование 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, которую вы хотите использовать.

1
27.01.2020, 19:34

Вот один, который я считаю полезным:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq
2
27.01.2020, 19:34

Сzsh:

typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))

printf '%s\n' $dirs
1
27.01.2020, 19:34

Вы можете использовать переключатель -execдля запуска dirname и получения имени каталога вместо имени файла. Это имеет дополнительное преимущество совместимости с POSIX.

find. -name "*file*" -exec dirname {} \;
2
03.03.2021, 21:10

Я нашел этот вариант, который не использует sortили uniqueполезным

find. -type d -print0 | xargs -0 -I{} find {} -maxdepth 1 -iname '*.log' -print -quit

Преимущество в том, что вам не нужно ждать, пока будет пройдено все дерево перед сортировкой.

  1. Найти все каталогиfind. -type d -print0

  2. Для каждого каталога | xargs -0 -I{}найдите файл в текущем каталоге -maxdepth 1, соответствующий шаблону-iname '*.log'(без учета регистра ).Если найдено, напечатайте имя файла -printи прекратите перемещение по этому каталогу quit

    .

В качестве альтернативы

find. -type d -print0 | xargs -0 -I// find // -maxdepth 1 -iname '*.log' -exec dirname {} \; -quit

который просто печатает имя родительского каталога, вдохновленный ответом Snowbuilders .

0
25.04.2021, 22:17

Теги

Похожие вопросы