Trouver des répertoires qui NE contiennent PAS de fichier

47

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 se trouve dans /Artist/Album/cover.(jpg|png) .

Je souhaite parcourir tous les répertoires de second niveau et trouver ceux qui ne comportent pas de couverture. Au deuxième niveau, je veux dire que je ne m'inquiète pas si /Britney Spears/ n'a pas de cover.jpg, mais je me soucierais que /Britney Spears/In The Zone/ n'en ait pas.

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

    
posée Oli 06.10.2012 - 01:27
la source

3 réponses

11

Simple, ça se passe. La liste suivante répertorie les 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, laissant une liste de répertoires nécessitant des couvertures.

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

Hourra.

Notes:

  • Les arguments de

    comm sont les suivants:

    • -1 supprime les lignes uniques à fichier1
    • -2 supprime les lignes uniques à fichier2
    • -3 supprime les lignes apparaissant dans les deux fichiers
  • comm ne prend que les fichiers, d’où la méthode d’entrée kooky <(...) . Cela canalise le contenu via un vrai fichier [temporaire].

  • comm a besoin d'une entrée triée ou 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.* , il pourrait donc y avoir des entrées en double. sort -u ébranle rapidement ceux-ci à un. La seconde trouvaille sera toujours unique.

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

  • find et comm sont tous deux un peu en désordre avec leur sortie. Le sed final est là pour nettoyer les choses, vous êtes donc laissé avec Artist/Album . Cela peut ou peut ne pas être souhaitable pour vous.

réponse donnée Oli 06.10.2012 - 01:39
la source
65

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

Utilisez find avec test -e your_file pour vérifier si un fichier existe. Par exemple, vous recherchez les répertoires qui ne contiennent aucun 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 pourrait être jPg , png ...

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

Explication:

  • Vous devez créer un shell sh pour chaque répertoire, car le raccordement est impossible avec find
  • ls -1 "{}" génère uniquement les noms de fichiers du répertoire find traverse actuellement
  • egrep (au lieu de grep ) utilise des expressions régulières étendues; -i rend la recherche insensible, -q lui fait omettre toute sortie
  • "^cover\.(jpg|png)$" est le modèle de recherche. Dans cet exemple, il correspond par ex. cOver.png , Cover.JPG ou cover.png . Le . doit être échappé sinon cela signifie qu'il correspond à un caractère tout . ^ marque le début de la ligne, $ sa fin

Autres exemples de modèle 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 NOT Cover.jpg (la sensibilité à la casse n'est pas désactivée)
  • egrep -iq "^(cover|front)\.jpg$" : correspond par ex. front.jpg , Cover.JPG mais non Cover.PNG

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

    
réponse donnée phoibos 06.10.2012 - 01:55
la source
7

Ceci est beaucoup plus agréable à résoudre avec la globalisation 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’avez pas de fichiers parasites dans cette belle structure. Le répertoire en cours contient uniquement des sous-répertoires d'artiste, et ceux-ci ne contiennent que des sous-répertoires d'album. Nous pouvons alors faire quelque chose comme ceci:

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

La syntaxe <(...) correspond à la 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 comme un fichier. Nous pouvons donc exécuter deux programmes et prendre leur diff, sans enregistrer leur sortie dans des fichiers temporaires. Le programme diff pense qu'il fonctionne avec deux fichiers, mais en fait, il lit deux canaux.

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

Test de fonctionnement:

$ 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 cela par défaut, * se développe lui-même s'il ne correspond à rien. Cela peut être résolu avec set -o nullglob de Bash.

    
réponse donnée Anon 06.10.2012 - 06:20
la source

Lire d'autres questions sur les étiquettes