Comment trouver des fichiers dans des sous-répertoires et les trier par nom de fichier dans une seule commande?

9

Résultat d'une recherche normale utilisant find . ! -path "./build*" -name "*.txt":

./tool/001-sub.txt
./tool/000-main.txt
./zo/001-int.txt
./zo/id/002-and.txt
./as/002-mod.txt

et lorsqu'il est trié avec sort -n:

./as/002-mod.txt
./tool/000-main.txt
./tool/001-sub.txt
./zo/001-int.txt
./zo/id/002-and.txt

cependant, la sortie souhaitée est:

./tool/000-main.txt
./zo/001-int.txt
./tool/001-sub.txt
./zo/id/002-and.txt
./as/002-mod.txt

ce qui signifie que la sortie est triée en fonction du nom de fichier uniquement , mais les informations de dossier doivent être conservées dans le cadre de la sortie.

Edit : rendre l'exemple plus compliqué car la structure du sous-répertoire peut inclure plus d'un niveau.

unode
la source
2
Voir cette question que j'ai posée sur SO: stackoverflow.com/questions/3222810/…
camh
@camh - si possible, je voudrais utiliser uniquement des commandes unix. En tout cas, ma question est à peu près une copie de la vôtre. Pouvez-vous transférer la meilleure solution sur ce fil (garder un lien vers l'original de toute façon) pour que je puisse marquer est la solution?
onode
Si @Shawn apporte les modifications que j'ai suggérées dans mon commentaire (utilisez -printfplutôt que awk), je pense que c'est la meilleure solution. J'ai retravaillé mon implémentation d'origine pour utiliser cette méthode.
camh

Réponses:

9

Vous devez trier par le dernier champ (considéré /comme un séparateur de champ). Malheureusement, je ne peux pas penser à un outil qui puisse le faire lorsque le nombre de champs varie (si seulement sort -kpouvait prendre des valeurs négatives).

Pour contourner cela, vous devrez faire une déco-décorer-décorer. Autrement dit, prenez le nom de fichier et placez-le au début suivi d'un séparateur de champ, puis effectuez un tri, puis supprimez la première colonne et le séparateur de champ.

find . ! -path "./build*" -name "*.txt" |\
    awk -vFS=/ -vOFS=/ '{ print $NF,$0 }' |\
    sort -n -t / |\
    cut -f2- -d/

Cette awkcommande indique que le séparateur de champ FS est défini sur /; cela affecte la façon dont il lit les champs. Le séparateur de champ de sortie OFS est également défini sur /; cela affecte la façon dont il imprime les enregistrements. L'instruction suivante indique imprimer la dernière colonne ( NFest le nombre de champs dans l'enregistrement, donc il se trouve que c'est également l'index du dernier champ) ainsi que l'enregistrement entier ( $0est l'enregistrement entier); il les imprimera avec l'OFS entre eux. Ensuite, la liste est sortéditée, en /tant que séparateur de champ - puisque nous avons le nom de fichier en premier dans l'enregistrement, il sera trié en fonction de cela. Ensuite, le cutchamp imprime uniquement les champs 2 jusqu'à la fin et est à nouveau traité /comme le séparateur de champ.

Shawn J. Goff
la source
3
Comme c'est avec find (1), vous pouvez ignorer la partie awk et utiliser-printf '%f/%p\n'
camh
en effet notre configuration est un peu plus compliquée. Il inclut des profondeurs de sous-répertoires variables. Modifié la question pour refléter ce fait. Mes excuses pour ne pas avoir inclus cela au début.
onode
1
@Unode: la solution de Shawn gère très bien la profondeur variable, c'est la solution canonique à ce problème (jusqu'à des variations mineures).
Gilles 'SO- arrête d'être méchant'
4

J'utiliserais les fichiers '-printf' pour sortir le nom et le chemin, trier par nom et couper le nom dans une dernière étape. '###' est juste un marqueur, pour aider à couper.

find -name "*.txt" -printf "%f###%p\n" | sort -n | sed 's/.*###//'

% f imprime le nom du fichier,% p le chemin complet.

J'ai simplifié la recherche de commande pour la mettre sur une seule ligne, bien sûr, vous quitteriez la ! -path "./build*"partie.

Utilisateur inconnu
la source
3

En zsh ≥4.3.10:

print -l -- **/*.txt~build*(oe\''REPLY=${REPLY:t}'\')
  • **/*.txtcorrespond *.txtdans le répertoire courant et ses sous-répertoires de manière récursive .
  • ~build* exclut les correspondances dont le texte commence par build*(comme ! -path './build*'). (Vous devez d' setopt extended_globabord.)
  • (oe\''…'\')est un qualificateur de glob de tri . REPLY=…construit la chaîne à trier à partir de la chaîne à renvoyer.
  • ${REPLY:t}est le nom de base («queue») du chemin.
Gilles 'SO- arrête d'être méchant'
la source
Beaucoup de magie concaténée. Intéressant mais nous sommes limités à la syntaxe sh. +1
unode