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.
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.
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
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/'
Pourquoi ne pas essayer ceci:
find / -name '*f*' -printf "%h\n" | sort -u
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.
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*
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/
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!
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.
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
En voici une que je trouve utile:
find . -type f -name "*somefile*" | xargs dirname | sort | uniq
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
.
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*
$ 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 echo
quelle que vous souhaitiez utiliser.
Avec zsh
:
typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))
printf '%s\n' $dirs