Comment puis-je trouver toutes les extensions de fichier distinctes dans une hiérarchie de dossiers?

235

Sur une machine Linux, je voudrais parcourir une hiérarchie de dossiers et obtenir une liste de toutes les extensions de fichiers distinctes en son sein.

Quelle serait la meilleure façon d'y parvenir à partir d'un shell?

GloryFish
la source

Réponses:

347

Essayez ceci (je ne sais pas si c'est la meilleure façon, mais cela fonctionne):

find . -type f | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort -u

Cela fonctionne comme suit:

  • Trouver tous les fichiers du dossier actuel
  • Imprime l'extension des fichiers le cas échéant
  • Faire une liste triée unique
Ivan Nevostruev
la source
8
juste pour référence: si vous voulez exclure certains répertoires de la recherche (par exemple .svn), utilisez find . -type f -path '*/.svn*' -prune -o -print | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort -u source
Dennis Golomazov
Les espaces ne feront aucune différence. Chaque nom de fichier sera sur une ligne distincte, donc le délimiteur de liste de fichiers sera "\ n" pas un espace.
Ivan Nevostruev
1
Sous Windows, cela fonctionne mieux et est beaucoup plus rapide que find: dir / s / b | perl -ne 'imprime 1 $ si m /\.([^^.\\\\\++ )$/' | sort -u
Ryan Shillington
3
git variation de la réponse: utiliser à la git ls-tree -r HEAD --name-onlyplace defind
jakub.g
8
Une variation, cela montre la liste avec le nombre par extension:find . -type f | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort | uniq -c | sort -n
marcovtwout
55

Pas besoin de tuyau sort, awk peut tout faire:

find . -type f | awk -F. '!a[$NF]++{print $NF}'
SiegeX
la source
Je ne fais pas fonctionner cela comme un alias, je reçois awk: une erreur de syntaxe au niveau de la ligne source 1 est >>>! A [] <<< awk: renflouement à la ligne source 1. Qu'est-ce que je fais mal? Mon alias est défini comme suit: alias file_ext = "find. -Type f -name ' . ' | Awk -F. '! A [$ NF] ++ {print $ NF}'"
user2602152
2
@ user2602152, le problème est que vous essayez d'entourer l'intégralité d'une ligne avec des guillemets pour la aliascommande, mais la commande elle-même utilise déjà des guillemets dans la commande find. Pour résoudre ce problème, j'utiliserais bashla syntaxe de chaîne littérale de:alias file_ext=$'find . -type f -name "*.*" | awk -F. \'!a[$NF]++{print $NF}\''
SiegeX
cela ne fonctionne pas si un sous-répertoire a un. dans son nom et le fichier n'a pas d'extension de fichier. Exemple: lorsque nous courons depuis maindir, cela échouera pourmaindir/test.dir/myfile
Nelson Teixeira le
1
@NelsonTeixeira Ajoutez -printf "%f\n"à la fin de la commande 'find' et relancez votre test.
SiegeX
41

Version récursive:

find . -type f | sed -e 's/.*\.//' | sed -e 's/.*\///' | sort -u

Si vous voulez des totaux (combien de fois l'extension a-t-elle été vue):

find . -type f | sed -e 's/.*\.//' | sed -e 's/.*\///' | sort | uniq -c | sort -rn

Non récursif (dossier unique):

for f in *.*; do printf "%s\n" "${f##*.}"; done | sort -u

J'ai basé cela sur ce message sur le forum , le crédit devrait y aller.

ChristopheD
la source
Génial! fonctionne également pour mon scénario git, essayait de comprendre quel type de fichiers j'ai touché lors du dernier commit:git show --name-only --pretty="" | sed -e 's/.*\.//' | sed -e 's/.*\///' | sort -u
vulcan raven
30

Powershell:

dir -recurse | select-object extension -unique

Merci à http://kevin-berridge.blogspot.com/2007/11/windows-powershell.html

Simon R
la source
20
L'OP a dit "Sur une machine Linux"
Forbesmyester
9
en fait, il existe un prowershell pour linux: github.com/Microsoft/PowerShell-DSC-for-Linux
KIC
4
Comme écrit, cela ramassera également les répertoires qui ont un .(par exemple, jquery-1.3.4ils apparaîtront comme .4dans la sortie). Changez pour dir -file -recurse | select-object extension -uniqueobtenir uniquement les extensions de fichier.
mcw
1
@Forbesmyester: Les gens avec Windows (comme moi) trouveront cette question. C'est donc utile.
Roel
1
Merci pour la réponse de Powershell. Vous ne supposez pas comment les utilisateurs recherchent. Beaucoup de gens ont voté pour une raison
Mahesh
20

Mon alternative sans souci, sans sed, sans Perl, sans Python, conforme à POSIX:

find . -type f | rev | cut -d. -f1 | rev  | tr '[:upper:]' '[:lower:]' | sort | uniq --count | sort -rn

L'astuce est qu'elle inverse la ligne et coupe l'extension au début.
Il convertit également les extensions en minuscules.

Exemple de sortie:

   3689 jpg
   1036 png
    610 mp4
     90 webm
     90 mkv
     57 mov
     12 avi
     10 txt
      3 zip
      2 ogv
      1 xcf
      1 trashinfo
      1 sh
      1 m4v
      1 jpeg
      1 ini
      1 gqv
      1 gcs
      1 dv
Ondra Žižka
la source
sur mac, uniqn'a pas le drapeau complet --count, mais -cfonctionne très bien
worc
12

Trouvez tout avec un point et n'affichez que le suffixe.

find . -type f -name "*.*" | awk -F. '{print $NF}' | sort -u

si vous savez que tous les suffixes ont 3 caractères,

find . -type f -name "*.???" | awk -F. '{print $NF}' | sort -u

ou avec sed affiche tous les suffixes avec un à quatre caractères. Remplacez {1,4} par la plage de caractères attendue dans le suffixe.

find . -type f | sed -n 's/.*\.\(.\{1,4\}\)$/\1/p'| sort -u
user224243
la source
1
Pas besoin de trier le tuyau, awk peut tout faire: trouver. -type f -nom " . " | awk -F. '! a [$ NF] ++ {print $ NF}'
SiegeX
@SiegeX La vôtre devrait être une réponse distincte. Il a trouvé que cette commande fonctionnait le mieux pour les grands dossiers, car elle imprimait les extensions telles qu'elles les trouvaient. Mais notez que cela devrait être: -nom " . "
Ralf
@Ralf done, réponse publiée ici . Pas tout à fait sûr de ce que vous entendez par la -name "."chose parce que c'est déjà ce que c'est
SiegeX
Je voulais dire que ce devrait être -name "*. *", Mais StackOverflow supprime les caractères *, ce qui s'est probablement produit également dans votre commentaire.
Ralf
Il semble que cela devrait être la réponse acceptée, awk est préférable à perl comme outil de ligne de commande et il embrasse la philosophie unix de canalisation de petits programmes interopérables dans des procédures cohérentes et lisibles.
Jon z
7

Ajout de ma propre variation au mix. Je pense que c'est le plus simple du lot et peut être utile lorsque l'efficacité n'est pas une grande préoccupation.

find . -type f | grep -o -E '\.[^\.]+$' | sort -u
gkb0986
la source
1
+1 pour la portabilité, bien que l'expression régulière soit assez limitée, car elle ne correspond qu'à des extensions constituées d'une seule lettre. Il semble préférable d'utiliser l'expression régulière de la réponse acceptée:$ find . -type f | grep -o -E '\.[^.\/]+$' | sort -u
mMontu
1
D'accord. Je me suis un peu relâché là-bas. Modification de ma réponse pour corriger l'erreur que vous avez repérée.
gkb0986
cool. J'ai chenge des guillemets pour les doubles guillemets, mettre à jour les biraries et les dépendances grep (car fourni avec git est obsolète) et maintenant cela fonctionne sous windows. se sentir comme un utilisateur linux.
msangel
5

En Python, utiliser des générateurs pour de très gros répertoires, y compris des extensions vides, et obtenir le nombre de fois où chaque extension apparaît:

import json
import collections
import itertools
import os

root = '/home/andres'
files = itertools.chain.from_iterable((
    files for _,_,files in os.walk(root)
    ))
counter = collections.Counter(
    (os.path.splitext(file_)[1] for file_ in files)
)
print json.dumps(counter, indent=2)
Andres Restrepo
la source
5

J'ai essayé un tas de réponses ici, même la "meilleure" réponse. Ils sont tous restés en deçà de ce que je cherchais spécifiquement. Donc, en plus des 12 dernières heures passées en code regex pour plusieurs programmes et en lisant et en testant ces réponses, c'est ce que j'ai trouvé qui fonctionne exactement comme je le souhaite.

 find . -type f -name "*.*" | grep -o -E "\.[^\.]+$" | grep -o -E "[[:alpha:]]{2,16}" | awk '{print tolower($0)}' | sort -u
  • Recherche tous les fichiers qui peuvent avoir une extension.
  • Greps seulement l'extension
  • Greps pour les extensions de fichiers entre 2 et 16 caractères (ajustez simplement les nombres s'ils ne correspondent pas à vos besoins). Cela permet d'éviter les fichiers de cache et les fichiers système (le bit de fichier système est pour rechercher en prison).
  • Awk pour imprimer les extensions en minuscules.
  • Triez et apportez uniquement des valeurs uniques. À l'origine, j'avais essayé d'essayer la réponse awk, mais cela doublerait l'impression des éléments dont la sensibilité à la casse variait.

Si vous avez besoin d'un nombre d'extensions de fichiers, utilisez le code ci-dessous

find . -type f -name "*.*" | grep -o -E "\.[^\.]+$" | grep -o -E "[[:alpha:]]{2,16}" | awk '{print tolower($0)}' | sort | uniq -c | sort -rn

Bien que ces méthodes prennent un certain temps à compléter et ne soient probablement pas les meilleures façons de résoudre le problème, elles fonctionnent.

Mise à jour: les extensions de fichier longues selon @ alpha_989 entraîneront un problème. Cela est dû à l'expression régulière "[[: alpha:]] {3,6}". J'ai mis à jour la réponse pour inclure l'expression régulière "[[: alpha:]] {2,16}". Cependant, toute personne utilisant ce code doit être conscient que ces nombres sont le minimum et le maximum de la durée pendant laquelle l'extension est autorisée pour la sortie finale. Tout ce qui est en dehors de cette plage sera divisé en plusieurs lignes dans la sortie.

Remarque: le message d'origine a lu "- Greps pour les extensions de fichier entre 3 et 6 caractères (ajustez simplement les nombres s'ils ne correspondent pas à vos besoins). Cela permet d'éviter les fichiers de cache et les fichiers système (le bit du fichier système est pour rechercher en prison). "

Idée: pourrait être utilisée pour trouver des extensions de fichier sur une longueur spécifique via:

 find . -type f -name "*.*" | grep -o -E "\.[^\.]+$" | grep -o -E "[[:alpha:]]{4,}" | awk '{print tolower($0)}' | sort -u

Où 4 est la longueur des extensions de fichier à inclure, puis recherchez également toutes les extensions au-delà de cette longueur.

Shinrai
la source
La version de comptage est-elle récursive?
Fernando Montoya
@Shinrai, En général, fonctionne bien. mais si vous avez des extensions de fichiers aléatoires qui sont vraiment longues telles que .download, cela divisera le ".download" en 2 parties et rapportera 2 fichiers, un qui est "downlo" et un autre qui est "ad"
alpha_989
@ alpha_989, cela est dû à l'expression régulière "[[: alpha:]] {3,6}" provoquera également un problème avec les extensions de moins de 3 caractères. Adaptez-vous à ce dont vous avez besoin. Personnellement, je dirais que 2,16 devrait fonctionner dans la plupart des cas.
Shinrai
Merci d'avoir répondu .. Ouais .. c'est ce que j'ai réalisé plus tard. Cela a bien fonctionné après l'avoir modifié de la même manière que ce que vous avez mentionné.
alpha_989
3

Puisqu'il existe déjà une autre solution qui utilise Perl:

Si vous avez installé Python, vous pouvez également faire (à partir du shell):

python -c "import os;e=set();[[e.add(os.path.splitext(f)[-1]) for f in fn]for _,_,fn in os.walk('/home')];print '\n'.join(e)"
ChristopheD
la source
2

Jusqu'à présent, aucune des réponses ne traite correctement des noms de fichiers avec des sauts de ligne (à l'exception de ChristopheD, qui vient d'arriver au moment où je tapais ceci). Ce qui suit n'est pas un one-liner shell, mais fonctionne et est assez rapide.

import os, sys

def names(roots):
    for root in roots:
        for a, b, basenames in os.walk(root):
            for basename in basenames:
                yield basename

sufs = set(os.path.splitext(x)[1] for x in names(sys.argv[1:]))
for suf in sufs:
    if suf:
        print suf

la source
2

Je ne pense pas que celui-ci ait encore été mentionné:

find . -type f -exec sh -c 'echo "${0##*.}"' {} \; | sort | uniq -c
Dmitry B.
la source
Cela serait probablement assez lent en raison de la création d'un nouveau processus pour chaque fichier.
Ondra Žižka
1

Je pense que la manière la plus simple et la plus directe est

for f in *.*; do echo "${f##*.}"; done | sort -u

Il est modifié sur la 3ème voie de ChristopheD.

Robert
la source
0

vous pouvez aussi faire ça

find . -type f -name "*.php" -exec PATHTOAPP {} +
jrock2004
la source
0

Je l'ai trouvé simple et rapide ...

   # find . -type f -exec basename {} \; | awk -F"." '{print $NF}' > /tmp/outfile.txt
   # cat /tmp/outfile.txt | sort | uniq -c| sort -n > tmp/outfile_sorted.txt
Diego Callejo
la source
0

La réponse acceptée utilise REGEX et vous ne pouvez pas créer une commande d'alias avec REGEX, vous devez la mettre dans un script shell, j'utilise Amazon Linux 2 et j'ai fait ce qui suit:

  1. Je mets le code de réponse accepté dans un fichier en utilisant:

    sudo vim find.sh

ajoutez ce code:

find ./ -type f | perl -ne 'print $1 if m/\.([^.\/]+)$/' | sort -u

enregistrez le fichier en tapant: :wq!

  1. sudo vim ~/.bash_profile

  2. alias getext=". /path/to/your/find.sh"

  3. :wq!

  4. . ~/.bash_profile

Chris Medina
la source