Trouver des répertoires qui ne contiennent pas de fichier

58

Oui, je trie ma musique. J'ai tout arrangé magnifiquement dans le mantra suivant: /Artist/Album/Track - Artist - Title.extet s'il en existe une, la couverture reste en place /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 ne me soucie pas de /Britney Spears/ne pas avoir de cover.jpg, mais je me soucierais de /Britney Spears/In The Zone/ne pas en avoir.

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 findexemple inverse .

Oli
la source
pour tous ceux qui sont intéressés par le téléchargement des jaquettes manquantes, il suffit d'installer le fichier launchpad.net/coverlovin et de remplacer le paramètre -print dans @phoibos answer par "-exec ./coverlovin.py {} \;"
Dror Cohen

Réponses:

81

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

Utilisez findavec test -e your_filepour vérifier si un fichier existe. Par exemple, vous recherchez des répertoires qui n'en contiennent 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 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 shpour chaque répertoire car la tuyauterie n’est pas possible avecfind
  • ls -1 "{}"sort seulement les noms de fichiers du répertoire findqui traverse actuellement
  • egrep(au lieu de grep) utilise des expressions régulières étendues; -irend la recherche insensible à la casse, -qfait en sorte qu'elle omette toute sortie
  • "^cover\.(jpg|png)$"est le motif de recherche. Dans cet exemple, cela correspond par exemple à cOver.png, Cover.JPGou cover.png. Le .doit être échappé sinon cela signifie qu'il correspond à n'importe quel caractère. ^marque le début de la ligne, $sa fin

Autres exemples de motif de recherche pour egrep :

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

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

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

phoibos
la source
Absolument magnifique - avec le problème qu’il n’est pas flexible de choisir entre des cas ou des extensions différentes (j’ai essayé un joker, mais pas de solution). Je me demande s'il existe une meilleure alternative à test.
Oli
1
Hmm, vous pouvez imbriquer la découverte avec cela, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;mais c'est assez dégoûtant en termes d'optimisation. Cela fonctionne cependant.
Oli
J'ai trouvé que vous pouviez passer testune charge de -o EXPRESSIONrequêtes OR, par exemple ... test -e "{}/cover.jpg" -o -e "{}/cover.png"ce qui est mieux que de faire une recherche complète mais reste sensible à la casse.
Oli
Il convient de noter que la comparaison des performances de ce dernier (deux tests, selon mon dernier commentaire) avec les deux autres solutions (commd trouver et commbing globbing) est de loin la plus lente (684 ms contre 40 ms et 50 ms respectivement)
Oli
La solution d'origine en réponse prend une seconde et se casse dans les circonstances qui ont un $nom de répertoire (Ke $ ha, par exemple).
Oli
12

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:

  • commLes arguments de sont les suivants:

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

  • comma besoin d'entrées triées ou cela ne fonctionne pas et findne garantit en aucun cas une commande. Il doit également être unique. La première findopération pourrait rechercher plusieurs fichiers cover.*afin d'éviter des doublons. sort -urapidement les remue à un. La deuxième découverte sera toujours unique.

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

  • findet commsont tous les deux un peu en désordre avec leur sortie. La finale sedest là pour nettoyer les choses afin que vous restiez avec Artist/Album. Cela peut ou peut ne pas être souhaitable pour vous.

Oli
la source
2
votre premier findpeut éventuellement être simplifié find ~/Music/ -iname 'cover.*' -printf '%h\n', en évitant le besoin dirname. mais dirnameest pratique ailleurs.
Tom
Merci @ Tom, c'est beaucoup plus rapide que de partir partout (29 ms contre 734 ms sur mon répertoire de musique - les deux découvertes "chaleureuses")
Oli
9

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 la substitution de 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. Nous pouvons donc exécuter deux programmes et prendre leurs diff, sans enregistrer leur sortie dans des fichiers temporaires. Le diffprogramme pense travailler avec deux fichiers, mais en réalité, il lit deux canaux.

La commande qui produit l'entrée de droite dans diff, printf "%s\n" */*répertorie simplement les répertoires de l'album. La commande de gauche effectue une itération dans les *.coverchemins 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/bet foo/baront pas cover.jpg.

Il y a des cas de coins cassés, comme celui qui par défaut se *développe s'il ne correspond à rien. Ceci peut être résolu avec Bash's set -o nullglob.

Anon
la source
Excuses pour la réponse tardive. C'est une idée intéressante mais: les couvertures peuvent être en png et jpb et ne seraient pas commplus propres que diff?
Oli
comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)semble être un compromis raisonnable sans aucun difffluff. Il est cependant un peu plus lent que ma double trouvaille.
Oli
0
ls --color=never */*.txt | sed 's|/.*||' | sort -u -n > withtxt.txt
ls --color=never -d * | sort -u -n > all.txt
diff all.txt withtxt.txt

Affiche tous les répertoires ne contenant pas de fichiers txt.

Roel Van de Paar
la source