Как я могу выполнить поиск в ширину, используя `find`?

16

-depthНачальная школа до findзаставляет его выполнить поиск в глубине.

Однако последовательность по умолчанию - это не поиск в ширину.

Последовательность по умолчанию может быть неофициально описана как «обход в глубину, который обрабатывает узлы, когда они впервые встречаются, а не во время обратного отслеживания».

Я действительно нуждаюсь в ширине первого поиска. Как я могу заставить findсебя так себя вести?


Для иллюстрации со следующей настройкой:

$ mkdir -p alpha/{bravo,charlie,delta}
$ touch alpha/charlie/{alpha,beta,gamma,phi}

find имеет следующее поведение по умолчанию:

$ find alpha
alpha
alpha/charlie
alpha/charlie/alpha
alpha/charlie/phi
alpha/charlie/beta
alpha/charlie/gamma
alpha/delta
alpha/bravo

и -depthон выполняет следующее:

$ find alpha -depth
alpha/charlie/alpha
alpha/charlie/phi
alpha/charlie/beta
alpha/charlie/gamma
alpha/charlie
alpha/delta
alpha/bravo
alpha

Тем не менее, я хочу следующую (фиктивную) опцию:

$ find alpha -bfs
alpha
alpha/charlie
alpha/delta
alpha/bravo
alpha/charlie/alpha
alpha/charlie/phi
alpha/charlie/beta
alpha/charlie/gamma

Другими словами, мне нужно findобработать / сообщить обо всех файлах / каталогах на заданной глубине, прежде чем продолжить.

Как я могу это сделать?

Wildcard
источник
Не с find(по крайней мере, не только find). Вы хотите только перечислить файлы, или вы хотите использовать другие праймериз?
Жиль "ТАК ... перестать быть злым"
@ Жиль, на самом деле я понял, что -bfsэто не совсем то, что мне нужно ... У меня есть простой скрипт, который генерирует индекс для большого проекта GitLab, подходящий для включения в GitLab Wiki. Это делает заголовки иерархически основанными на именах каталогов. Он прекрасно работает, за исключением того, что в приведенной выше структуре файла примера он помещается deltaпод charlieподзаголовком, а не под родительским alphaзаголовком.
Wildcard
Еще одна странная вещь, что мой findвыход в алфавитном порядке. Понятия не имею почему ...
Wildcard
Тем не менее, я думаю, что это -bfs может пригодиться, даже если это не совсем подходит для этого варианта использования.
Wildcard
2
Я реализовал такой инструмент: BFS . Он еще не на 100% совместим с GNU find, но он уже есть.
Тавиан Барнс

Ответы:

6

Вы можете сделать это только с подстановочными знаками оболочки. Создайте шаблон с постепенно увеличивающимся уровнем каталогов.

pattern='*'
set -- $pattern
while [ $# -ne 1 ] || [ "$1" != "$pattern" ]; do
  for file; do
    …
  done
  pattern="$pattern/*"
  set -- $pattern
done

Это пропускает точечные файлы. Используйте FIGNORE='.?(.)'в ksh, shopt -s dotglobв bash или setopt glob_dotsв zsh, чтобы включить их.

Предостережения:

  • Это взорвет память, если файлов много.
  • Это рекурсивно пересекает символические ссылки на каталоги.

Если вы хотите выбрать порядок или каталоги и не-каталоги, а производительность не критична, вы можете сделать два прохода и протестировать [ -d "$file" ]каждый проход.

Жиль "ТАК - перестань быть злым"
источник
@Wildcard Да, я сделал.
Жиль "ТАК - перестань быть злым"
1
Ницца! Еще одно почти тривиальное предостережение: он не сможет обработать файл, который является единственным файлом в каталоге, если файл имеет буквальное имя *. :)
Wildcard
@Wildcard О, да, я забыл упомянуть об этом. Используйте bash или zsh с nullglobи используйте (($#))в качестве условия цикла, чтобы избежать этого крайнего случая.
Жиль "ТАК ... перестать быть злым"
5

# cat ./bfind

#!/bin/bash
i=0
while results=$(find "$@" -mindepth $i -maxdepth $i) && [[ -n $results ]]; do
  echo "$results"
  ((i++))
done

Это работает путем увеличения глубины findи повторения, я думаю, что это может повторить результаты, но может быть легко отфильтровано

user239175
источник
Извините, я не знал о механизме форматирования. Во всяком случае, на самом деле это не повторяется, я думаю, потому что оно обрезает что-то меньше, чем mindepth
user239175
3

Вы можете findотсортировать данные по типу, который сортируется в основном по количеству /символов в пути. Например,

find alpha |
awk '{n=gsub("/","/",$0);printf "%04d/%s\n",n,$0}' |
sort -t/ |
sed 's|[^/]*/||'

Используется awkдля добавления префикса к пути с количеством слешей и sedудаления этого префикса в конце.

На самом деле, поскольку вы, вероятно, хотите, чтобы содержимое каталога alpha/charlie+было указано после alpha/charlie, вам нужно сказать sort -t/ -k1,1 -k2,2 -k3,3 -k4,4до желаемой глубины.

meuh
источник
0

Другой ответ, основанный не на 'find', а на bash - сначала используйте "длину родительского каталога", затем сортируйте по альфе.

Ответ не совсем совпадает, так как ваши результаты имеют "Чарли, Браво, Дельта", но мне было интересно, должно ли это быть "Браво, Чарли, Дельта" в альфа-порядке.

paths_breadth_first() {
  while IFS= read -r line; do
    dirn=${line%/*}         ## dirname(line)
    echo ${#dirn},$line     ## len(dirn),line
  done | sort -n | cut -d ',' -f 2-
}

Что производит

  $ cat /tmp/yy | paths_breadth_first 
  alpha
  alpha/bravo
  alpha/charlie
  alpha/delta
  alpha/charlie/alpha
  alpha/charlie/beta
  alpha/charlie/gamma
  alpha/charlie/phi
qneill
источник