Como posso fazer uma pesquisa abrangente usando `find`?

16

O -depthprincipal para fazer findcom que ele realize uma pesquisa profunda.

No entanto, a sequência padrão não é uma pesquisa pela primeira vez.

A sequência padrão pode ser descrita informalmente como uma "travessia profunda que trata de nós quando eles são encontrados pela primeira vez, em vez de fazê-lo durante o retorno".

Tenho uma necessidade real de primeira pesquisa de largura. Como posso me findcomportar dessa maneira?


Para ilustração, com a seguinte configuração:

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

find tem o seguinte comportamento padrão:

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

e com -depth, ele executa da seguinte maneira:

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

No entanto, o que eu quero é a seguinte opção (fictícia):

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

Em outras palavras, eu preciso findprocessar / relatar todos os arquivos / diretórios em uma determinada profundidade antes de prosseguir.

Como posso fazer isso?

Curinga
fonte
Não com find(pelo menos, não com apenas find). Deseja apenas listar os arquivos ou deseja usar outras primárias?
Gilles 'SO- stop be evil'
@ Gilles, na verdade percebi que -bfsnão seria exatamente o que eu precisava ... Eu tenho um script simples que gera um índice para um projeto GitLab grande, adequado para inclusão no Wiki do GitLab. Faz os cabeçalhos hierarquicamente com base nos nomes de diretório. Funciona muito bem, exceto que na estrutura do arquivo de exemplo acima, ela seria colocada deltano charliesubtítulo, em vez de no alphacabeçalho pai .
Wildcard
Outra coisa estranha é que minha findsaída é classificada em ordem alfabética. Não faço ideia porque ...
Wildcard
Ainda assim, acho que -bfs pode ser útil, mesmo que não se encaixe perfeitamente nesse caso de uso.
Wildcard
2
Eu implementei essa ferramenta: bfs . Ainda não é 100% compatível com os recursos do GNU, mas está chegando lá.
Tavian Barnes

Respostas:

6

Você pode fazer isso apenas com curingas de shell. Crie um padrão com progressivamente mais níveis de diretório.

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

Isso perde arquivos de ponto. Use FIGNORE='.?(.)'no ksh, shopt -s dotglobno bash ou setopt glob_dotsno zsh para incluí-los.

Ressalvas:

  • Isso irá explodir a memória se houver muitos arquivos.
  • Isso percorre links simbólicos para diretórios recursivamente.

Se você quiser escolher a ordem ou os diretórios e os não diretórios, e o desempenho não for crítico, faça duas passagens e teste [ -d "$file" ]em cada passagem.

Gilles 'SO- parar de ser mau'
fonte
@ Wildcard Sim, eu fiz.
Gilles 'SO- stop be evil'
1
Agradável! Mais uma advertência quase trivial: ele falhará ao processar um arquivo que é o único arquivo em um diretório se o arquivo for literalmente nomeado *. :)
Caractere curinga
@ Wildcard Oh, sim, eu esqueci de mencionar isso. Use bash ou zsh com nullglobe use (($#))como condição de loop para evitar esse caso de borda.
Gilles 'SO- stop be evil'
5

# cat ./bfind

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

Isso funciona aumentando a profundidade finde repetindo, acho que pode repetir resultados, mas pode ser filtrado facilmente

user239175
fonte
Desculpe, eu não sabia sobre o mecanismo de formatação. De qualquer forma, na verdade ele não repetir Acho que é porque ele corta nada menos do que mindepth
user239175
3

Você pode colocar o seu findem uma classificação classificada principalmente pelo número de /caracteres no nome do caminho. Por exemplo,

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

Isso usa awkpara prefixar o nome do caminho com o número de barras e sedpara remover esse prefixo no final.

Na verdade, como você provavelmente deseja que o conteúdo do diretório alpha/charlie+seja listado depois alpha/charlie, é necessário dizer sort -t/ -k1,1 -k2,2 -k3,3 -k4,4até a profundidade desejada.

meuh
fonte
0

Outra resposta não baseada em 'find', mas no bash - use o "comprimento do diretório pai" primeiro e depois classifique por alfa.

A resposta não corresponde exatamente porque seus resultados têm "charlie, bravo, delta", mas imaginei se deveria ser "bravo, charlie, delta" em ordem alfabética.

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

Isso produz

  $ 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
fonte