Trova le directory che NON contengono un file


58

Sì, sto sistemando la mia musica. Ho tutto sistemato magnificamente nel seguente mantra: /Artist/Album/Track - Artist - Title.exte se ne esiste uno, la copertina si trova dentro /Artist/Album/cover.(jpg|png).

Voglio scansionare tutte le directory di secondo livello e trovare quelle che non hanno una copertina. Al secondo livello, voglio dire che non mi importa se /Britney Spears/non ha una cover.jpg, ma mi importa se /Britney Spears/In The Zone/non ne ho uno.

Non preoccuparti per il download della copertina (è un progetto divertente per me domani) Mi interessa solo il glorioso bash-fuiness di un findesempio inverso .


per chiunque sia interessato a scaricare le copertine che mancano basta installare launchpad.net/coverlovin e sostituire la -print nella risposta @phoibos con "-exec ./coverlovin.py {} \;"
Dror Cohen,

Risposte:


81

Caso 1: conosci il nome esatto del file da cercare

Utilizzare findcon test -e your_fileper verificare l'esistenza di un file. Ad esempio, cerchi le directory che non hanno cover.jpgin esse:

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

È sensibile al maiuscolo / minuscolo.

Caso 2: vuoi essere più flessibile

Non sei sicuro del caso e l'estensione potrebbe essere jPg, png...

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

Spiegazione:

  • È necessario generare una shell shper ogni directory poiché non è possibile eseguire il piping durante l'utilizzofind
  • ls -1 "{}"produce solo i nomi dei file della directory che findsta attraversando
  • egrep(invece di grep) usa espressioni regolari estese; -irende insensibile la distinzione tra maiuscole e minuscole, -qomette qualsiasi output
  • "^cover\.(jpg|png)$"è il modello di ricerca. In questo esempio, corrisponde ad esempio cOver.png, Cover.JPGo cover.png. La .deve essere sfuggito altrimenti vuol dire che corrisponde a qualsiasi carattere. ^segna l'inizio della linea, la $sua fine

Altri esempi di modelli di ricerca per egrep :

Sostituire la egrep -i -q "^cover\.(jpg|png)$"parte con:

  • egrep -i -q "cover\.(jpg|png)$": Partite Inoltre cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": Corrisponde cover.png, cover.jpgma NON Cover.jpg(la distinzione tra maiuscole e minuscole non è disattivata)
  • egrep -iq "^(cover|front)\.jpg$": corrisponde ad esempio front.jpg, Cover.JPGma non Cover.PNG

Per ulteriori informazioni su questo, controlla le espressioni regolari .


Assolutamente bellissimo - con il problema che non è flessibile scegliere tra casi o estensioni diverse (ho provato un carattere jolly ma no-go). Mi chiedo se ci sia un'alternativa migliore a test.
Oli

1
Hmm puoi annidare la ricerca con questo, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;ma è piuttosto sporco in termini di ottimizzazione. Funziona però.
Oli

Ho scoperto che puoi passare testun sacco di -o EXPRESSIONquery OR ... ad esempio: test -e "{}/cover.jpg" -o -e "{}/cover.png"che è meglio che fare una ricerca completa ma è comunque sensibile al maiuscolo / minuscolo.
Oli

Dovrei notare che confrontare le prestazioni di questo (due test, per il mio ultimo commento) con le altre due soluzioni (trovato e commosso travolgente) questo è di gran lunga il più lento (rispettivamente 684ms contro 40ms e 50ms)
Oli

La soluzione di risposta originale impiega un secondo e si interrompe in circostanze che hanno $il nome della directory (ad esempio Ke $ ha).
Oli

12

Semplice, traspare. Quanto segue ottiene un elenco di directory con la copertina e lo confronta con un elenco di tutte le directory di secondo livello. Le righe che compaiono in entrambi i "file" vengono soppresse, lasciando un elenco di directory che necessitano di copertine.

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

Evviva.

Appunti:

  • commGli argomenti sono i seguenti:

    • -1 sopprimere le righe univoche per file1
    • -2 sopprimere le righe univoche per file2
    • -3 sopprimere le righe visualizzate in entrambi i file
  • commaccetta solo file, quindi il <(...)metodo di input kooky . Questo convoglia il contenuto tramite un vero file [temporaneo].

  • commnecessita di input ordinati o non funziona e findnon garantisce in alcun modo un ordine. Deve anche essere unico. La prima findoperazione potrebbe trovare più file per cover.*cui potrebbero esserci voci duplicate. sort -uincrespa rapidamente quelli fino a uno. La seconda scoperta sarà sempre unica.

  • dirnameè uno strumento utile per ottenere la directory di un file senza ricorrere a sed(et al).

  • finde commsono entrambi un po 'confusi con il loro output. Il finale sedè lì per ripulire le cose in modo da restare Artist/Album. Questo può o meno essere desiderabile per te.


2
il tuo primo findpuò eventualmente essere semplificato find ~/Music/ -iname 'cover.*' -printf '%h\n', evitando la necessità di dirname. sebbene dirnamesia utile altrove.
Tom,

Grazie @Tom, è molto più veloce che sfaldare ovunque (29ms contro 734ms sulla mia musica dir - entrambi i risultati "caldi")
Oli

9

Questo è molto più bello da risolvere con globbing che con 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

Supponiamo ora di non avere file randagi in questa bella struttura. La directory corrente contiene solo le sottodirectory degli artisti e quelle contengono solo le sottodirectory degli album. Quindi possiamo fare qualcosa del genere:

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

La <(...)sintassi è la sostituzione del processo di Bash: ti permette di usare un comando al posto di un argomento di file. Ti consente di trattare l'output di un comando come un file. Quindi possiamo eseguire due programmi e prendere il loro diff, senza salvare il loro output in file temporanei. Il diffprogramma pensa che funzioni con due file, ma in realtà sta leggendo da due pipe.

Il comando che produce l'input della mano destra in diff, printf "%s\n" */*elenca solo le directory degli album. Il comando a sinistra scorre i *.coverpercorsi e stampa i nomi delle loro directory.

Prova:

$ 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

Ah, le directory a/be foo/barnon hanno cover.jpg.

Ci sono alcuni casi angolari rotti, come quello di default si *espande a se stesso se non corrisponde a nulla. Questo può essere risolto con Bash's set -o nullglob.


Ci scusiamo per la risposta tardiva. È un'idea interessante ma: le copertine possono essere in png e jpb e non sarebbero più commpulite di diff?
Oli

comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)sembra un ragionevole compromesso senza alcuna difflanugine. È, tuttavia, un po 'più lento del mio doppio ritrovamento.
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

Mostrerà tutte le directory che non contengono file txt.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.