Jak mogę przeprowadzić pełne wyszukiwanie za pomocą `find`?

17

-depthPodstawowej do findpowoduje, że aby przeprowadzić przeszukiwanie w głąb.

Jednak domyślna sekwencja nie jest szerokim wyszukiwaniem.

Domyślna sekwencja może być nieformalnie opisana jako „przejście przez pierwszą głębokość, która obsługuje węzły, gdy są one po raz pierwszy napotykane, zamiast robić to podczas cofania”.

Naprawdę potrzebuję pierwszego wyszukiwania. Jak mogę się findzachowywać w ten sposób?


Na przykład z następującą konfiguracją:

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

find ma następujące domyślne zachowanie:

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

i przy -depthzachowuje się następująco:

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

Chcę jednak następującej (fikcyjnej) opcji:

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

Innymi słowy muszę findprzetworzyć / zgłosić wszystkie pliki / katalogi na określonej głębokości, zanim przejdę dalej.

Jak mogę to zrobić?

Dzika karta
źródło
Nie z find(przynajmniej nie tylko find). Czy chcesz tylko wyświetlić listę plików, czy chcesz użyć innych podstawowych?
Gilles 'SO - przestań być zły'
@Gilles, właściwie zdałem sobie sprawę, -bfsże nie byłoby to dokładnie to, czego potrzebuję ... Mam prosty skrypt, który generuje indeks dużego projektu GitLab, odpowiedni do włączenia na Wiki GitLab. To sprawia, że ​​nagłówki są hierarchicznie oparte na nazwach katalogów. Działa świetnie, z tym wyjątkiem, że w powyższej przykładowej strukturze pliku zostałby umieszczony deltapod nagłówkiem charliezamiast alphanagłówka nadrzędnego .
Wildcard
Kolejną dziwną rzeczą jest to, że moje findwyniki sortowane alfabetycznie. Nie mam pojęcia, dlaczego ....
Wildcard
Myślę jednak, że -bfs może się przydać, nawet jeśli nie pasuje idealnie do tego przypadku użycia.
Wildcard
2
Wdrożyłem takie narzędzie: bfs . Nie jest jeszcze w 100% kompatybilny z funkcjami GNU find, ale się tam pojawia.
Tavian Barnes,

Odpowiedzi:

6

Możesz to zrobić za pomocą symboli wieloznacznych powłoki. Stwórz wzór z coraz większą liczbą poziomów katalogów.

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

To pomija pliki kropkowe. Użyj FIGNORE='.?(.)'w ksh, shopt -s dotglobw bash lub setopt glob_dotsw zsh, aby je uwzględnić.

Ostrzeżenia:

  • Spowoduje to wysadzenie pamięci, jeśli jest dużo plików.
  • To rekursywnie przegląda symboliczne linki do katalogów.

Jeśli chcesz wybrać kolejność lub katalogi i inne niż katalogi, a wydajność nie jest krytyczna, możesz wykonać dwa przejścia i przetestować [ -d "$file" ]każde przejście.

Gilles „SO- przestań być zły”
źródło
@Wildcard Tak, zrobiłem.
Gilles 'SO - przestań być zły'
1
Miły! Jeszcze jedno trywialne zastrzeżenie: Przetwarzanie pliku, który jest samotnym plikiem w katalogu, zakończy się niepowodzeniem, jeśli plik będzie miał dosłownie nazwę *. :)
Wildcard
@Wildcard Och, tak, zapomniałem o tym wspomnieć. Użyj bash lub zsh nullglobi użyj (($#))jako warunku pętli, aby uniknąć tego przypadku krawędzi.
Gilles 'SO - przestań być zły'
5

# cat ./bfind

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

Działa to poprzez zwiększenie głębokości findi powtarzanie, myślę, że może powtarzać wyniki, ale może być łatwo filtrowane

użytkownik239175
źródło
Niestety nie wiedziałem o mechanizmie formatowania. W każdym razie, tak naprawdę się nie powtarza, ponieważ myślę, że odcina to mniej niż myślenie
user239175
3

Możesz potokować swoje finddo sortowania, które sortuje przede wszystkim według liczby /znaków w nazwie ścieżki. Na przykład,

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

Służy to awkdo prefiksu nazwy ścieżki liczbą ukośników i seddo usunięcia tego prefiksu na końcu.

W rzeczywistości, ponieważ prawdopodobnie chcesz, aby zawartość katalogu alpha/charlie+była później wymieniona alpha/charlie, musisz powiedzieć sort -t/ -k1,1 -k2,2 -k3,3 -k4,4do żądanej głębokości.

meuh
źródło
0

Inna odpowiedź nie oparta na „znajdź”, ale na bash - najpierw użyj „długości katalogu nadrzędnego”, a następnie posortuj według alfy.

Odpowiedź nie do końca pasuje, ponieważ wyniki zawierają „charlie, bravo, delta”, ale zastanawiałem się, czy powinno to być „bravo, charlie, delta” w kolejności alfa.

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-
}

To produkuje

  $ 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
źródło