web-dev-qa-db-fra.com

Trouver des répertoires qui ne contiennent pas de fichier

Oui, je trie ma musique. J'ai tout arrangé magnifiquement dans le mantra suivant: /Artist/Album/Track - Artist - Title.ext et s'il en existe un, la couverture est assise dans /Artist/Album/cover.(jpg|png).

Je veux parcourir tous les répertoires de deuxième niveau et trouver ceux qui n'ont pas de couverture. Au deuxième niveau, je veux dire que je me fiche de savoir si /Britney Spears/ n’a pas de cover.jpg, mais je me soucierais de si /Britney Spears/In The Zone/ n'en avait pas.

Ne vous inquiétez pas du téléchargement de la couverture (c'est un projet amusant pour moi demain). Je ne me soucie que de la glorieuse bash-fuiness d'un exemple inverse-ish findname__.

57
Oli

Simple, ça se passe. Ce qui suit obtient une liste de répertoires avec la couverture et la compare à une liste de tous les répertoires de second niveau. Les lignes qui apparaissent dans les deux "fichiers" sont supprimées, ce qui laisse une liste de répertoires à couvrir.

comm -3 \
    <(find ~/Music/ -iname 'cover.*' -printf '%h\n' | sort -u) \
    <(find ~/Music/ -maxdepth 2 -mindepth 2 -type d | sort) \
| sed 's/^.*Music\///'

Hourra.

Remarques:

  • Les arguments de comm sont les suivants:

    • -1 supprime les lignes uniques à file1
    • -2 supprime les lignes uniques à file2
    • -3 supprime les lignes apparaissant dans les deux fichiers
  • comm ne prend que des fichiers, d’où la méthode de saisie kooky <(...). Cela dirige le contenu via un fichier réel [temporaire].

  • comm a besoin d'une entrée triée ou cela ne fonctionne pas et find ne garantit en aucun cas une commande. Il doit également être unique. La première opération find pourrait trouver plusieurs fichiers pour cover.*, de sorte qu'il pourrait y avoir des entrées en double. sort -u les remue rapidement à un. La deuxième découverte sera toujours unique.

  • dirname est un outil pratique pour obtenir le répertoire d'un fichier sans avoir recours à sed (et al).

  • find et comm sont un peu compliqués avec leur sortie. Le nom final sed est là pour nettoyer les choses afin que vous restiez avec Artist/Album. Cela peut ou peut ne pas être souhaitable pour vous.

11
Oli

Cas 1: Vous connaissez le nom de fichier exact à rechercher

Utilisez findavec test -e your_file pour vérifier si un fichier existe. Par exemple, vous recherchez des répertoires ne contenant pas cover.jpg:

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec test -e "{}/cover.jpg" ';' -print

C'est sensible à la casse cependant.

Cas 2: Vous voulez être plus flexible

Vous n'êtes pas sûr du cas et l'extension peut être jPgname__, pngname __...

find base_dir -mindepth 2 -maxdepth 2 -type d '!' -exec sh -c 'ls -1 "{}"|egrep -i -q "^cover\.(jpg|png)$"' ';' -print

Explication:

  • Vous devez générer un Shell shpour chaque répertoire, car la tuyauterie n'est pas possible avec findname__
  • ls -1 "{}" sort seulement les noms de fichiers du répertoire que findest en train de parcourir
  • egrep(au lieu de grepname__) utilise des expressions régulières étendues; -i rend la recherche insensible à la casse, -q permet d'omettre tout résultat
  • "^cover\.(jpg|png)$" est le modèle de recherche. Dans cet exemple, cela correspond par exemple à cOver.png, Cover.JPG ou cover.png. Le . doit être échappé, sinon cela signifie qu'il correspond à à tout caractère . ^ marque le début de la ligne, $ sa fin

Autres exemples de motifs de recherche pour egrep :

Remplacez la partie egrep -i -q "^cover\.(jpg|png)$" par:

  • egrep -i -q "cover\.(jpg|png)$": correspond également à cd_cover.png, album_cover.JPG ...
  • egrep -q "^cover\.(jpg|png)$": correspond à cover.png, cover.jpg, mais PAS à Cover.jpg (la sensibilité à la casse n'est pas désactivée)
  • egrep -iq "^(cover|front)\.jpg$": correspond par exemple à front.jpg, Cover.JPG mais pas Cover.PNG

Pour plus d'informations à ce sujet, consultez Expressions régulières .

80
phoibos

C'est beaucoup plus agréable à résoudre avec Globbing qu'avec Find.

$ cd ... # to the directory one level above the album/artist structure

$ echo */*/*.cover   # lists all the covers

$ printf "%s\n" */*/*.cover # lists all the covers, one per line

Supposons maintenant que vous n’ayez pas de fichiers parasites dans cette belle structure. Le répertoire actuel ne contient que des sous-répertoires d'artistes, et ceux-ci ne contiennent que des sous-répertoires d'albums. Ensuite, nous pouvons faire quelque chose comme ceci:

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)

La syntaxe <(...) est Substitution du processus Bash: elle vous permet d’utiliser une commande à la place d’un argument de fichier. Il vous permet de traiter la sortie d'une commande en tant que fichier. Ainsi, nous pouvons exécuter deux programmes et prendre leurs diff, sans enregistrer leur sortie dans des fichiers temporaires. Le programme diffpense qu'il fonctionne avec deux fichiers, mais en fait, il lit deux canaux.

La commande qui génère l’entrée droite de diffname__, printf "%s\n" */* répertorie uniquement les répertoires de l’album. La commande de gauche effectue une itération dans les chemins *.cover et affiche leurs noms de répertoire.

Essai:

$ find .   # let's see what we have here
.
./a
./a/b
./foo
./foo/bar
./foo/baz
./foo/baz/cover.jpg

$ diff  <(for x in */*/cover.jpg; do echo "$(dirname "$x")" ; done) <(printf "%s\n" */*)
0a1,2
> a/b
> foo/bar

Aha, les répertoires a/b et foo/bar n'ont pas de cover.jpg.

Il y a des cas de coins cassés, comme par défaut * s’agrandit à lui-même s’il ne correspond à rien. Ce problème peut être résolu avec le set -o nullglob de Bash.

8
Anon