Suchen Sie nach Verzeichnissen, die KEINE Datei enthalten

58

Ja, ich sortiere meine Musik. Ich habe alles in dem folgenden Mantra wunderschön arrangiert: /Artist/Album/Track - Artist - Title.extund wenn es eines gibt, sitzt das Cover darin /Artist/Album/cover.(jpg|png).

Ich möchte alle Verzeichnisse der zweiten Ebene durchsuchen und diejenigen finden, die kein Cover haben. Mit der zweiten Ebene meine ich, es ist mir egal, ob /Britney Spears/es keine cover.jpg gibt, aber es wäre mir wichtig, wenn /Britney Spears/In The Zone/es keine gäbe .

Mach dir keine Sorgen über das Herunterladen des Covers (das ist ein lustiges Projekt für mich von morgen). Mir geht es nur um die glorreiche Schüchternheit eines inversen findBeispiels.

Oli
quelle
Wenn Sie die fehlenden Cover herunterladen möchten, installieren Sie einfach launchpad.net/coverlovin und ersetzen Sie den -print in @phoibos answer durch "-exec ./coverlovin.py {} \;"
Dror Cohen

Antworten:

81

Fall 1: Sie kennen den genauen Dateinamen, nach dem Sie suchen müssen

Verwenden Sie findmit test -e your_fileüberprüfen , ob eine Datei vorhanden ist . Zum Beispiel suchen Sie nach Verzeichnissen, die keine enthalten cover.jpg:

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

Es ist jedoch case sensitive.

Fall 2: Sie möchten flexibler sein

Sie sind sich nicht sicher, und die Erweiterung könnte sein jPg, png...

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

Erläuterung:

  • Sie müssen shfür jedes Verzeichnis eine Shell erzeugen, da bei der Verwendung kein Piping möglich istfind
  • ls -1 "{}"gibt nur den Dateinamen des Verzeichnisses aus, finddas gerade durchsucht wird
  • egrep(anstelle von grep) verwendet erweiterte reguläre Ausdrücke; -iMacht die Suche unabhängig von Groß- und Kleinschreibung, -qlässt keine Ausgabe zu
  • "^cover\.(jpg|png)$"ist das Suchmuster. In diesem Beispiel entspricht es zum Beispiel cOver.png, Cover.JPGoder cover.png. Das .muss maskiert werden, sonst bedeutet es, dass es mit einem beliebigen Zeichen übereinstimmt . ^markiert den Anfang der Linie, $ihr Ende

Andere Suchmusterbeispiele für egrep :

Ersetzen Sie das egrep -i -q "^cover\.(jpg|png)$"Teil durch:

  • egrep -i -q "cover\.(jpg|png)$": Auch passt cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": Übereinstimmungen cover.png, cover.jpgaber NICHT Cover.jpg(Groß- / Kleinschreibung ist nicht deaktiviert)
  • egrep -iq "^(cover|front)\.jpg$": Passt zB front.jpg, Cover.JPGaber nicht Cover.PNG

Weitere Informationen hierzu finden Sie unter Reguläre Ausdrücke .

Phoibos
quelle
Absolut schön - mit dem Problem, dass es nicht flexibel ist, zwischen Fällen oder verschiedenen Erweiterungen zu wählen (ich habe versucht, einen Platzhalter zu verwenden, aber nichts zu tun). Ich frage mich, ob es eine bessere Alternative gibt test.
Oli
1
Hmm, Sie können den Fund damit verschachteln, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;aber das ist in Bezug auf die Optimierung ziemlich dreckig. Es funktioniert aber.
Oli
Ich habe festgestellt, dass Sie für OP-Abfragen testeine -o EXPRESSIONMenge übergeben können ... z test -e "{}/cover.jpg" -o -e "{}/cover.png".
Oli
Ich sollte beachten, dass der Vergleich der Leistung (zwei Tests, nach meinem letzten Kommentar) mit den beiden anderen Lösungen (Comm'd Find und Comm'd Globbing) bei weitem die langsamste ist (684 ms gegenüber 40 ms bzw. 50 ms)
Oli
Die ursprüngliche In-Answer-Lösung übernimmt eine Sekunde und unterbricht Umstände, die sich $im Verzeichnisnamen befinden (z. B. Ke $ ha).
Oli
12

Einfach, es stellt sich heraus. Im Folgenden wird eine Liste der Verzeichnisse mit dem Cover abgerufen und diese mit einer Liste aller Verzeichnisse der zweiten Ebene verglichen. Zeilen, die in beiden "Dateien" vorkommen, werden unterdrückt und es verbleibt eine Liste der Verzeichnisse, für die Deckblätter erforderlich sind.

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

Hurra.

Anmerkungen:

  • commDie Argumente von lauten wie folgt:

    • -1 Unterdrückt Zeilen, die nur für file1 gelten
    • -2 Unterdrücke Zeilen, die für file2 eindeutig sind
    • -3 Zeilen unterdrücken, die in beiden Dateien vorkommen
  • commNimmt nur Dateien, daher die verrückte <(...)Eingabemethode. Dies leitet den Inhalt über eine echte [temporäre] Datei weiter.

  • commbenötigt sortierte Eingaben oder es funktioniert nicht und findgarantiert in keinem Fall eine Bestellung. Es muss auch einzigartig sein. Beim ersten findVorgang wurden mehrere Dateien gefunden, cover.*sodass möglicherweise doppelte Einträge vorhanden sind. sort -ukräuselt diese schnell auf eins. Der zweite Fund wird immer einzigartig sein.

  • dirnameist ein praktisches Tool, um das Verzeichnis einer Datei abzurufen, ohne auf sed(et al.) zurückzugreifen.

  • findund commbeide sind ein bisschen chaotisch mit ihrer Ausgabe. Das Finale sedist da, um die Dinge aufzuräumen, damit du übrig bleibst Artist/Album. Dies kann für Sie wünschenswert oder nicht wünschenswert sein.

Oli
quelle
2
Ihre erste findkann möglicherweise vereinfacht werden find ~/Music/ -iname 'cover.*' -printf '%h\n', um die Notwendigkeit zu vermeiden dirname. obwohl dirnameanderswo praktisch.
Tom
Danke @Tom, das ist viel schneller als das Abfliegen überall (29 ms gegenüber 734 ms in meinem Musikverzeichnis - beide "warme" Funde)
Oli
9

Dies ist mit Globbing viel besser zu lösen als mit 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

Nehmen wir nun an, Sie haben keine Streudateien in dieser schönen Struktur. Das aktuelle Verzeichnis enthält nur Interpreten-Unterverzeichnisse und diese enthalten nur Album-Unterverzeichnisse. Dann können wir so etwas machen:

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

Die <(...)Syntax lautet Bash-Prozessersetzung: Sie können einen Befehl anstelle eines Dateiarguments verwenden. Sie können die Ausgabe eines Befehls als Datei behandeln. Wir können also zwei Programme ausführen und deren Diff verwenden, ohne ihre Ausgabe in temporären Dateien zu speichern. Das diffProgramm glaubt, dass es mit zwei Dateien arbeitet, aber tatsächlich liest es aus zwei Pipes.

Der Befehl, der die rechte Hand eingegeben erzeugt diff, printf "%s\n" */*listet nur die Album - Verzeichnisse. Der Befehl für die linke Hand durchläuft die *.coverPfade und gibt deren Verzeichnisnamen aus.

Testlauf:

$ 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, die a/bund foo/barVerzeichnisse haben keine cover.jpg.

Es gibt einige Fälle mit unterbrochenen Ecken, die sich standardmäßig *zu sich selbst erweitern, wenn sie mit nichts übereinstimmen. Dies kann mit Bash's behoben werden set -o nullglob.

Anon
quelle
Entschuldigung für die späte Antwort. Es ist eine interessante Idee, aber: Covers können in PNG und JPB sein und wären nicht commsauberer als diff?
Oli
comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)scheint wie ein vernünftiger Kompromiss ohne diffFlusen. Es ist jedoch etwas langsamer als mein Doppelfund.
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

Zeigt alle Verzeichnisse an, in denen sich keine txt-Dateien befinden.

Roel Van de Paar
quelle