Comment puis-je faire une recherche en premier en utilisant `find`?

17

Le -depthprincipal qui lui findfait effectuer une recherche en profondeur d'abord.

Cependant, la séquence par défaut n'est pas une recherche étendue.

La séquence par défaut pourrait être décrite de manière informelle comme une «traversée en profondeur d'abord qui gère les nœuds lorsqu'ils sont rencontrés pour la première fois plutôt que de le faire pendant le retour en arrière».

J'ai un réel besoin de recherche approfondie. Comment puis-je me findcomporter de cette façon?


Pour illustration, avec la configuration suivante:

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

find a le comportement par défaut suivant:

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

et avec -depth, il fonctionne comme suit:

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

Cependant, ce que je veux, c'est l'option (fictive) suivante:

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

En d'autres termes, je dois findtraiter / signaler tous les fichiers / répertoires à une profondeur donnée avant de poursuivre.

Comment puis-je faire ceci?

Caractère générique
la source
Pas avec find(du moins, pas seulement find). Souhaitez-vous uniquement lister les fichiers ou souhaitez-vous utiliser d'autres primaires?
Gilles 'SO- arrête d'être méchant'
@ Gilles, en fait, j'ai réalisé que -bfsce ne serait pas tout à fait ce dont j'ai besoin ... J'ai un script simple qui génère un index pour un grand projet GitLab, adapté à l'inclusion dans le Wiki GitLab. Il rend les en-têtes hiérarchiquement basés sur les noms de répertoire. Cela fonctionne très bien, sauf que dans l'exemple de structure de fichier ci-dessus, il serait placé deltasous le sous-en- charlietête, plutôt que sous l'en- alphatête parent .
Wildcard
Une autre chose étrange est que ma findsortie est triée par ordre alphabétique. Aucune idée pourquoi ....
Wildcard
Pourtant, je pense que cela -bfs pourrait être utile, même si cela ne correspond pas parfaitement à ce cas d'utilisation.
Wildcard
2
J'ai implémenté un tel outil: bfs . Il n'est pas encore compatible à 100% avec GNU find, mais il y arrive.
Tavian Barnes

Réponses:

6

Vous pouvez le faire avec simplement des caractères génériques shell. Construisez un modèle avec progressivement plus de niveaux de répertoire.

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

Cela manque les fichiers de points. Utilisez-les FIGNORE='.?(.)'en ksh, shopt -s dotgloben bash ou setopt glob_dotsen zsh pour les inclure.

Mises en garde:

  • Cela fera exploser la mémoire s'il y a beaucoup de fichiers.
  • Cela traverse récursivement les liens symboliques vers les répertoires.

Si vous souhaitez choisir l'ordre ou les répertoires et les non-répertoires et que les performances ne sont pas critiques, vous pouvez effectuer deux passes et tester [ -d "$file" ]à chaque passe.

Gilles 'SO- arrête d'être méchant'
la source
@Wildcard Oui, je l'ai fait.
Gilles 'SO- arrête d'être méchant'
1
Agréable! Une autre mise en garde presque triviale: il ne parviendra pas à traiter un fichier qui est le seul fichier dans un répertoire si le fichier est littéralement nommé *. :)
Wildcard
@Wildcard Oh, oui, j'ai oublié de le mentionner. Utilisez bash ou zsh avec nullglobet utilisez (($#))comme condition de boucle pour éviter ce cas de bord.
Gilles 'SO- arrête d'être méchant'
5

# cat ./bfind

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

Cela fonctionne en augmentant la profondeur findet en répétant, je pense que cela peut répéter les résultats, mais pourrait être filtré facilement

user239175
la source
Désolé, je ne connaissais pas le mécanisme de formatage. Quoi qu'il en soit, en fait, il ne se répète pas, je pense, car il coupe rien de moins que mindepth
user239175
3

Vous pouvez diriger votre finddans un tri qui trie principalement par le nombre de /caractères dans le chemin d'accès. Par exemple,

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

Cela permet awkde préfixer le chemin d'accès avec le nombre de barres obliques et sedde supprimer ce préfixe à la fin.

En fait, comme vous voulez probablement que le contenu du répertoire alpha/charlie+soit répertorié après alpha/charlie, vous devez dire sort -t/ -k1,1 -k2,2 -k3,3 -k4,4jusqu'à la profondeur souhaitée.

meuh
la source
0

Une autre réponse non basée sur 'find' mais sur bash - utilisez d'abord la "longueur du répertoire parent", puis triez par alpha.

La réponse ne correspond pas tout à fait car vos résultats ont "charlie, bravo, delta" mais je me demandais si ce devait être "bravo, charlie, delta" en ordre alpha.

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

Cela produit

  $ 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
la source