Encuentra directorios que NO contienen un archivo

58

Sí, estoy ordenando mi música. Tengo todo arreglado maravillosamente en el siguiente mantra: /Artist/Album/Track - Artist - Title.exty si existe uno, la cubierta se asienta /Artist/Album/cover.(jpg|png).

Quiero escanear todos los directorios de segundo nivel y encontrar los que no tienen una tapa. Por segundo nivel, quiero decir que no me importa si /Britney Spears/no tiene una cover.jpg, pero me importaría si /Britney Spears/In The Zone/no tuviera una.

No se preocupe por la descarga de la portada (ese es un proyecto divertido para mí mañana). Solo me importa la gloriosa bash-fuiness sobre un findejemplo inverso .

Oli
fuente
para cualquier persona que esté interesada en descargar las carátulas que faltan, simplemente instale launchpad.net/coverlovin y reemplace -print en la respuesta @phoibos con "-exec ./coverlovin.py {} \;"
Dror Cohen

Respuestas:

81

Caso 1: conoce el nombre exacto del archivo que debe buscar

Use findcon test -e your_filepara verificar si existe un archivo. Por ejemplo, busca directorios que no tienen cover.jpgen ellos:

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

Sin embargo, es sensible a mayúsculas y minúsculas.

Caso 2: quieres ser más flexible

No está seguro del caso, y la extensión podría ser jPg, png...

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

Explicación:

  • Debe generar un shell shpara cada directorio, ya que no se pueden establecer tuberías al usarfind
  • ls -1 "{}"genera solo los nombres de archivo del directorio que findestá atravesando actualmente
  • egrep(en lugar de grep) usa expresiones regulares extendidas; -ihace que el caso de búsqueda sea insensible, -qhace que omita cualquier salida
  • "^cover\.(jpg|png)$"Es el patrón de búsqueda. En este ejemplo, coincide cOver.png, por ejemplo , Cover.JPGo cover.png. El .debe escapar, de lo contrario significa que coincide con cualquier personaje. ^marca el inicio de la línea, $su final

Otros ejemplos de patrones de búsqueda para egrep :

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

  • egrep -i -q "cover\.(jpg|png)$": También coincide cd_cover.png, album_cover.JPG...
  • egrep -q "^cover\.(jpg|png)$": Coincide cover.png, cover.jpgpero NO Cover.jpg(la distinción entre mayúsculas y minúsculas no está desactivada)
  • egrep -iq "^(cover|front)\.jpg$": coincide front.jpg, por ejemplo , Cover.JPGpero no Cover.PNG

Para obtener más información sobre esto, consulte Expresiones regulares .

phoibos
fuente
Absolutamente hermoso, con el problema de que no es flexible elegir entre casos o extensiones diferentes (probé un comodín pero no se puede usar). Me pregunto si hay una mejor alternativa para test.
Oli
1
Hmm, puedes anidar el hallazgo con esto, -exec bash -c '[[ -n $(find "{}" -iname "cover.*") ]]' \;pero eso es bastante sucio en términos de optimización. Sin embargo, funciona.
Oli
Descubrí que puede pasar testuna gran cantidad de -o EXPRESSIONconsultas OR ... por ejemplo: lo test -e "{}/cover.jpg" -o -e "{}/cover.png"que es mejor que hacer una búsqueda completa, pero aún distingue entre mayúsculas y minúsculas.
Oli
Debo señalar que comparar el rendimiento de esto (dos pruebas, según mi último comentario) con las otras dos soluciones (comunicación encontrada y comunicación global) es, con mucho, la más lenta (684 ms frente a 40 ms y 50 ms respectivamente)
Oli
La solución original de respuesta toma más de un segundo y se rompe en circunstancias que tienen $en el nombre del directorio (Ke $ ha, por ejemplo).
Oli
12

Simple, transpira. A continuación se obtiene una lista de directorios con la portada y se compara con una lista de todos los directorios de segundo nivel. Las líneas que aparecen en ambos "archivos" se suprimen, dejando una lista de directorios que necesitan cubiertas.

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

¡Hurra!

Notas:

  • commLos argumentos son los siguientes:

    • -1 suprimir líneas exclusivas del archivo1
    • -2 suprimir líneas exclusivas de file2
    • -3 suprimir líneas que aparecen en ambos archivos
  • commsolo toma archivos, de ahí el <(...)método de entrada kooky . Esto canaliza el contenido a través de un archivo real [temporal].

  • commnecesita una entrada ordenada o no funciona y findde ninguna manera garantiza un pedido. También debe ser único. La primera findoperación podría encontrar varios archivos, por cover.*lo que podría haber entradas duplicadas. sort -urápidamente despeina a esos a uno. El segundo hallazgo siempre será único.

  • dirnamees una herramienta útil para obtener el directorio de un archivo sin recurrir a sed(et al).

  • findy commambos son un poco desordenados con su salida. La final sedestá ahí para limpiar las cosas para que te quedes Artist/Album. Esto puede o no ser deseable para usted.

Oli
fuente
2
el primero findposiblemente se pueda simplificar find ~/Music/ -iname 'cover.*' -printf '%h\n', evitando la necesidad de hacerlo dirname. aunque dirnamees útil en otros lugares.
Tom
Gracias @Tom, eso es mucho más rápido que bifurcarse en todas partes (29 ms frente a 734 ms en mi directorio de música - ambos hallazgos "cálidos")
Oli
9

Esto es mucho mejor para resolver con globbing que 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

Ahora suponga que no tiene archivos perdidos en esta bonita estructura. El directorio actual contiene solo subdirectorios de artistas, y esos contienen solo subdirectorios de álbumes. Entonces podemos hacer algo como esto:

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

La <(...)sintaxis es la sustitución del proceso Bash: le permite usar un comando en lugar de un argumento de archivo. Le permite tratar la salida de un comando como un archivo. Entonces podemos ejecutar dos programas y tomar su diff, sin guardar su salida en archivos temporales. El diffprograma cree que está trabajando con dos archivos, pero en realidad está leyendo desde dos canales.

El comando que produce la entrada a mano derecha diff, printf "%s\n" */*, solo muestra los directorios del álbum. El comando de la mano izquierda recorre las *.coverrutas e imprime sus nombres de directorio.

Prueba de funcionamiento:

$ 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

Ajá, los directorios a/by foo/barno tienen cover.jpg.

Hay algunos casos de esquina rota, por ejemplo, de forma predeterminada se *expande a sí mismo si no coincide con nada. Esto se puede solucionar con Bash's set -o nullglob.

Luego
fuente
Disculpas por la respuesta tardía. Es una idea interesante pero: las portadas pueden estar en png y jpb y, ¿no commsería más limpio que diff?
Oli
comm -3 <(printf "%s\n" */*/cover* | sed -r 's/\/[^\/]+$//' | sort -u) <(printf "%s\n" */*)parece un compromiso razonable sin ninguna de difflas pelusas. Sin embargo, es un poco más lento que mi doble hallazgo.
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

Mostrará todos los directorios que no tengan archivos txt en ellos.

Roel Van de Paar
fuente