Obtenir la liste des sous-répertoires contenant un fichier dont le nom contient une chaîne

45

Comment obtenir une liste des sous-répertoires contenant un fichier dont le nom correspond à un modèle particulier?

Plus précisément, je recherche des répertoires contenant un fichier avec la lettre "f" quelque part dans le nom du fichier.

Idéalement, la liste n'aurait pas de doublons et ne contiendrait que le chemin sans le nom de fichier.

Muhd
la source

Réponses:

43
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

Ce qui précède trouve tous les fichiers situés sous le répertoire en cours ( .) qui sont des fichiers normaux ( -type f) et ont fquelque part dans leur nom ( -name '*f*'). Ensuite, sedsupprime le nom du fichier, ne laissant que le nom du répertoire. Ensuite, la liste des répertoires est triée ( sort) et les doublons supprimés ( uniq).

La sedcommande consiste en un seul substitut. Il cherche des correspondances avec l'expression régulière /[^/]+$et remplace tout ce qui correspond à rien. Le signe dollar signifie la fin de la ligne. [^/]+'signifie un ou plusieurs caractères qui ne sont pas des barres obliques. Ainsi, /[^/]+$signifie tous les caractères de la dernière barre oblique à la fin de la ligne. En d'autres termes, cela correspond au nom du fichier à la fin du chemin complet. Ainsi, la commande sed supprime le nom du fichier, laissant inchangé le nom du répertoire dans lequel se trouvait le fichier.

Des simplifications

Beaucoup de sortcommandes modernes supportent un -udrapeau qui rend uniqinutile. Pour GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

Et pour MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

De plus, si votre findcommande le prend en charge, il est possible d’ findimprimer directement les noms de répertoire. Cela évite le besoin de sed:

find . -type f -name '*f*' -printf '%h\n' | sort -u

Version plus robuste (nécessite des outils GNU)

Les versions ci-dessus seront confondues avec les noms de fichiers qui incluent des nouvelles lignes. Une solution plus robuste consiste à effectuer le tri sur les chaînes terminées par NUL:

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
John1024
la source
J'ai beaucoup de fichiers qui rendent leur tri trop coûteux. Il est très uniqutile d’y ajouter des éléments en supprimant les lignes répétées qui sont déjà juxtaposées. find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'. Ou si vos outils sont un peu plus anciens, uniq peut ne pas avoir l'option -z. find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112
1
Utilisateurs MacOS: le drapeau sed n'est pas -r. Pour une raison quelconque, c'est -E
David
@ David Très vrai. Réponse mise à jour pour afficher -Epour MacOS.
John1024
23

Pourquoi ne pas essayer ceci:

find / -name '*f*' -printf "%h\n" | sort -u
Patrick Taylor
la source
Meilleure réponse. Entièrement compatible POSIX, contrairement à certaines réponses ci-dessus, ci-dessus, et gagne également le prix spécial The Shortest Pipeline :).
kkm
J'adorerais voir quelqu'un montrer le timing de ce match par rapport aux autres ci-dessus, car j'ai l'impression que c'est de loin le plus rapide.
Dlamblin
4
@kkm Je conviens que c'est la meilleure solution, mais les spécifications POSIXfind sont en réalité assez clairsemées - l' -printfopérateur n'est pas spécifié. Cela ne fonctionne pas avec BSD find. Donc, pas "entièrement compatible POSIX". (Quoique sort -u soit dans POSIX .)
Wildcard
8

Il existe essentiellement 2 méthodes que vous pouvez utiliser pour cela. L'un analysera la chaîne pendant que l'autre opèrera sur chaque fichier. L'analyse de la chaîne utilise un outil tel que grep, sedou awkva évidemment être plus rapide, mais voici un exemple montrant les deux, ainsi que la manière dont vous pouvez "profiler" les 2 méthodes.

Échantillon de données

Pour les exemples ci-dessous, nous utiliserons les données suivantes

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

Supprimer certains des *f*fichiers de dir1/*:

$ rm dir1/dir10{0..2}/*f*

Approche n ° 1 - Analyser via des chaînes

Ici , nous allons utiliser les outils suivants, find, grepet sort.

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

Approche n ° 2 - Analyser à l'aide de fichiers

Même chaîne d'outils qu'avant, sauf que cette fois, nous utiliserons la dirnameplace de grep.

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

Remarque: les exemples ci-dessus sont utilisés head -5pour limiter simplement la quantité de sortie que nous traitons pour ces exemples. Ils seraient normalement supprimés pour obtenir votre liste complète!

Comparer les résultats

Nous pouvons utiliser timepour regarder les 2 approches.

nom de répertoire

real        0m0.372s
user        0m0.028s
sys         0m0.106s

grep

real        0m0.012s
user        0m0.009s
sys         0m0.007s

Il est donc toujours préférable de manipuler les chaînes si possible.

Méthodes alternatives d'analyse de chaînes

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

sed

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

awk

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u
slm
la source
+1 Parce que cela fonctionne, mais il est intéressant que cela prenne plusieurs fois plus de temps que la réponse de @ John1024
Muhd
@Muhd - oui les appels à dirname sont lents. Je travaille sur une alternative.
slm
2

En voici un que je trouve utile:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq
Martin Tapp
la source
1

Cette réponse est basée sans vergogne sur slm answer. C'était une approche intéressante, mais qui présente une limitation si les noms de fichier et / ou de répertoire ont des caractères spéciaux (espace, demi-colonne, etc.). Une bonne habitude est à utiliser find /somewhere -print0 | xargs -0 someprogam.

Échantillon de données

Pour les exemples ci-dessous, nous utiliserons les données suivantes

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

Supprimer certains des *f*fichiers de dir1/*/:

rm dir1/dir\ 10{0..2}/*f*

Approche n ° 1 - Analyser à l'aide de fichiers

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

Remarque : les exemples ci-dessus sont utilisés head -5pour limiter simplement la quantité de sortie que nous traitons pour ces exemples. Ils seraient normalement supprimés pour obtenir votre liste complète! remplacez également la echocommande que vous souhaitez utiliser.

Franklin Piat
la source
1

Avec zsh:

typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))

printf '%s\n' $dirs
Stéphane Chazelas
la source