¿Cómo puedo encontrar carpetas que contengan archivos x de un tipo particular y generar esas rutas en OSX?

¿Cómo puedo encontrar carpetas que contengan archivos x de un tipo particular y generar esas rutas en OSX?

Tengo este script para OSX para buscar carpetas que solo contengan un archivo, y si ese archivo es un archivo de audio, genera la ruta del archivo de audio.

find "$1" -type d -exec sh -c '[[ $(find "$0" -mindepth 1 | wc -l) -eq 1 ]] 
&& [[ $(find "$0" -mindepth 1 -type d | wc -l) -eq 0 ]]  
&& find "$0"' {} \; |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"

es decir, usar como

./findodd.sh /Users/paul/Music

pero hay dos mejoras que necesito:

  1. ¿Qué puedo cambiar para que enumere los archivos en carpetas que contienen 2 archivos, 3 archivos, etc.? Sería incluso mejor si esto pudiera pasarse como parámetro.

  2. Actualmente busca carpetas que contienen solo un archivo, y ese archivo debe ser un archivo de audio, pero lo que realmente quiero que haga es encontrar una carpeta que contenga solo un archivo de audio, es decir, si la carpeta contiene tres archivos pero solo uno es un archivo de audio, desea que ese archivo de audio aparezca en la lista.

gracias pablo

Respuesta1

$ find
.
./folder3
./folder3/quux.txt
./folder1
./folder1/test.mp3
./folder1/test.txt
./folder1/test.wma
./folder2
./folder2/bar.txt
./folder2/foo.txt
./folder2/test.ogg

Ejecuciones de ejemplo:

$ ./findaudio.sh /tmp/findaudio 1
/tmp/findaudio/folder2/test.ogg

$ ./findaudio.sh /tmp/findaudio 2
/tmp/findaudio/folder1/test.mp3
/tmp/findaudio/folder1/test.wma

# The first parameter defaults to the current directory and
# the second parameter defaults to 1 so this works as well:
$ ./findaudio.sh
./folder2/test.ogg

Y aquí el código:

#!/bin/bash

shopt -s nullglob

find "${1:-.}" -type d | while read dir; do
        files=( "${dir}"/*.{mp4,mp3,ogg,flac,wma,m4a} )
        IFS=$'\n'
        (( ${#files[@]} == ${2:-1} )) && echo "${files[*]}"
done

Itera sobre todos los subdirectorios del directorio dado y utiliza globbing para leer todos los nombres de archivos de audio del subdirectorio actual en la matriz files. Si el tamaño de la matriz coincide con el valor deseado, simplemente imprime los nombres de los archivos separados por una nueva línea.

EDITAR: Este es mi enfoque anterior basado en la suposición de que deseaba imprimir las carpetas, no los nombres de archivos en cuestión. Lo dejaré aquí para referencia futura.

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -u
./folder2

Lo que esto hace es encontrar todos los archivos con las extensiones de audio enumeradas y solo imprimir los componentes de su directorio en lugar de la ruta completa. Esto le brinda una lista de carpetas principales para todos los archivos de audio. Salta uniqlíneas no únicas que deberían darle el resultado que busca, es decir, solo imprimir carpetas que contengan exactamente un archivo de audio.

En teoría, esto también debería ser bastante más rápido que el intento anterior.

Puede mejorar esto para satisfacer su primer punto contando las líneas duplicadas e imprimiendo solo las carpetas que coincidan con el recuento solicitado. Una solución ingenua sería:

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=1 '$1==count'
1 ./folder2

$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=2 '$1==count'
2 ./folder1

aunque podría ser mejor fusionar la uniqparte - y el lado derecho de la tubería en una sola awklínea.

Respuesta2

SEGUNDO INTENTO

Bien, después de probar esto yo mismo en mi propia carpeta de Música, esta es la solución para ambas solicitudes:

COMMAND='[[ $(find "$0" -maxdepth 2 |egrep "\.mp4|\.mp3|\.ogg|\.flac|\.wma|\.m4a"| wc -l) == '$2' ]] && echo "$0"'
find $1 -type d -exec sh -c "$COMMAND" {} \;

Así que había algunas cosas mal en tu guión:

  1. Estabas usando mindepthen lugar de maxdepth.
  2. Los puntos (.) en su egrep habrían coincidido con cualquier carácter. Entonces .wmahabría coincidido con 'Snowman.txt'.
  3. No fue necesario realizar la segunda prueba para el tipo 'd' ya que solo se pasan directorios al comando de shell.

Notas sobre mi guión:

  1. El uso es:findodd.sh <top_folder> <no_of_files>
  2. Las citas son fundamentales. La definición de COMMANDes en realidad 2 cadenas literales a cada lado del $2. Eso es realmente importante.
  3. Sólo enumera las carpetas que contienen los archivos, no los archivos en sí. Para hacer esto último, tendrías que reemplazarlo echo "$0"por otro find.

Ahora he estado probando en una máquina Arch Linux y mi shell es 'bash', así que no tengo idea si esto funcionará en OSX, ya que todos los shells NO son iguales. :-)


PRIMER INTENTO ANTES:

Mmmmm. No sé qué tan similar es OSX a Unix/Linux, pero lo intentaré.

Creo que la respuesta a ambas preguntas se encuentra en la primera prueba del comando 'sh -c'. Esa es la parte que dice:

$(find "$0" -mindepth 1 | wc -l) -eq 1

Para pasar un segundo parámetro a su secuencia de comandos para la cantidad de archivos, debería poder simplemente cambiar el '1' a $2, por lo que la prueba sería:

$(find "$0" -mindepth 1 | wc -l) -eq $2

No ponga comillas $2porque, de lo contrario, se interpretará como el segundo parámetro pasado al comando 'sh -c', no como su script.

La línea de comando sería entonces:

./findodd.sh /Users/paul/Music 2

Para lograr su segundo requisito, según tengo entendido, debe poner el egrepcomando en esa primera prueba, así:

$(find "$0" -mindepth 1 |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"| wc -l) -eq $2

Aunque quizá tengas que tener cuidado con las citas.

De todos modos, pruébalo y háznoslo saber.

Respuesta3

Podrías implementar esto en Python haciendo algo como esto:

#!/usr/bin/env python

import fnmatch
import os
import sys

if len(sys.argv) != 3 or \
        not sys.argv[1].isdigit() or \
        not os.path.exists(sys.argv[2]):
    print "Usage: %s [number of files] [search root]" % sys.argv[0]
    sys.exit(1)

num_files = int(sys.argv[1])
search_root = sys.argv[2]

# this must be a tuple to work with endswith()
audio_extensions = (
    'mp4',
    'mp3',
    'ogg',
    'flac',
    'wma',
    'm4a',
)

for dirpath, dirnames, filenames in os.walk(search_root):
    audio_files = [f for f in filenames if f.endswith(audio_extensions)]
    if len(audio_files) == num_files:
        print "\n".join([os.path.join(dirpath, f) for f in audio_files])

Si es así, chmod +x findodd.pypuede ejecutarlo de la misma manera que ejecuta su script actual, por ejemplo:

./findodd.py 1 /Users/paul/Music

información relacionada