web-dev-qa-db-fra.com

Récupère la liste des sous-répertoires contenant un fichier dont le nom contient une chaîne

Comment puis-je obtenir une liste des sous-répertoires qui contiennent 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.

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

Ce qui précède trouve tous les fichiers sous le répertoire courant (.) qui sont des fichiers normaux (-type f) et avoir f quelque part dans leur nom (-name '*f*'). Ensuite, sed supprime 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 commande sed consiste en un seul substitut. Il recherche des correspondances avec l'expression régulière /[^/]+$ et remplace tout ce qui correspond à cela par rien. Le signe dollar signifie la fin de la ligne. [^/]+' signifie un ou plusieurs caractères qui ne sont pas des barres obliques. Donc, /[^/]+$ signifie tous les caractères de la barre oblique finale à la fin de la ligne. En d'autres termes, cela correspond au nom de fichier à la fin du chemin d'accès complet. Ainsi, la commande sed supprime le nom du fichier, laissant inchangé le nom du répertoire dans lequel se trouvait le fichier.

Simplifications

De nombreuses commandes modernes sort prennent en charge un -u drapeau qui rend uniq inutile. 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 commande find la prend en charge, il est possible que find imprime directement les noms de répertoire. Cela évite d'avoir besoin de sed:

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

Version plus robuste (nécessite GNU)

Les versions ci-dessus seront confondues par les noms de fichiers qui incluent des retours à la ligne. 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/'
45
John1024

Pourquoi ne pas essayer ceci:

find / -name '*f*' -printf "%h\n" | sort -u
26
Patrick Taylor

Il existe essentiellement 2 méthodes que vous pouvez utiliser pour ce faire. L'un analysera la chaîne tandis que l'autre opérera sur chaque fichier. L'analyse de la chaîne utilise un outil tel que grep, sed ou awk va évidemment être plus rapide mais voici un exemple montrant les deux, ainsi que comment vous pouvez "profiler" "les 2 méthodes.

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

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

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

Approche n ° 1 - Analyse via des chaînes

Ici, nous allons utiliser les outils suivants, find, grep et 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 - Analyse à l'aide de fichiers

Même chaîne d'outils qu'avant, sauf que cette fois, nous utiliserons dirname au lieu 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 utilisent head -5 pour simplement limiter la quantité de sortie que nous traitons pour ces exemples. Ils seraient normalement supprimés pour obtenir votre liste complète!

Comparaison des résultats

Nous pouvons utiliser time pour jeter un œil aux 2 approches.

dirname

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 gérer les cordes si possible.

Méthodes alternatives d'analyse des 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
8
slm

En voici une que je trouve utile:

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

Cette réponse est sans vergogne basée sur la réponse slm. C'était une approche intéressante, mais elle a une limitation si les noms de fichiers et/ou de répertoires avaient des caractères spéciaux (espace, demi-colonne ...). Une bonne habitude consiste à utiliser find /somewhere -print0 | xargs -0 someprogam.

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

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

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

Approche n ° 1 - Analyse à 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

NOTE : Les exemples ci-dessus utilisent head -5 pour simplement limiter la quantité de sortie que nous traitons pour ces exemples. Ils seraient normalement supprimés pour obtenir votre liste complète! remplacez également la echoquelle que vous souhaitiez utiliser.

1
Franklin Piat

Avec zsh:

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

printf '%s\n' $dirs
1