Como posso encontrar pastas contendo x arquivos de um tipo específico e gerar esses caminhos no OSX?

Como posso encontrar pastas contendo x arquivos de um tipo específico e gerar esses caminhos no OSX?

Eu tenho esse script para OSX para encontrar pastas que contenham apenas um arquivo e, se esse arquivo for um arquivo de áudio, exibindo o caminho do arquivo de áudio

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"

ou seja, usar como

./findodd.sh /Users/paul/Music

mas há duas melhorias que preciso:

  1. O que posso mudar para listar arquivos em pastas contendo 2 arquivos, 3 arquivos ectera, seria ainda melhor se isso pudesse ser passado como parâmetro

  2. Atualmente ele encontra pastas contendo apenas um arquivo, e esse arquivo deve ser um arquivo de áudio, mas o que eu realmente quero fazer é encontrar uma pasta contendo apenas um arquivo de áudio, ou seja, se a pasta contém três arquivos, mas apenas um é um arquivo de áudio, eu deseja que esse arquivo de áudio seja listado.

obrigado Paulo

Responder1

$ 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

Execuções de exemplo:

$ ./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

E aqui o 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

Ele itera sobre todos os subdiretórios de um determinado diretório e usa globbing para ler todos os nomes de arquivos de áudio do subdiretório atual no array files. Se o tamanho do array corresponder ao valor desejado, ele apenas imprimirá os nomes dos arquivos separados por uma nova linha.

EDIT: Esta é a minha abordagem anterior baseada na suposição de que você queria imprimir as pastas, não os nomes dos arquivos em questão. Vou deixar aqui para referência futura.

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

O que isso faz é encontrar todos os arquivos com as extensões de áudio listadas e imprimir apenas os componentes do diretório em vez do caminho completo. Isso fornece uma lista de pastas principais para todos os arquivos de áudio. O uniqsalto sobre linhas não exclusivas deve fornecer o resultado que você procura, ou seja, apenas imprimir pastas que contenham exatamente um arquivo de áudio.

Em teoria, isso também deveria ser um pouco mais rápido do que sua tentativa anterior.

Você pode melhorar isso para satisfazer seu primeiro ponto contando as linhas duplicadas e imprimindo apenas as pastas que correspondem à contagem solicitada. Uma solução ingênua seria:

$ 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

embora possa ser melhor fundir a uniqparte -parte e o lado direito do tubo em uma única awklinha.

Responder2

SEGUNDA TENTATIVA

OK, depois de tentar fazer isso sozinho na minha própria pasta Música, esta é a solução para ambas as suas solicitações:

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" {} \;

Portanto, havia algumas coisas erradas com seu script:

  1. Você estava usando mindepthem vez de maxdepth.
  2. Os pontos (.) no seu egrep corresponderiam a qualquer caractere. Então .wmateria correspondido a 'Snowman.txt'.
  3. Você não precisou fazer o segundo teste para o tipo 'd', pois apenas os diretórios são passados ​​para o comando shell.

Notas sobre meu script:

  1. O uso é:findodd.sh <top_folder> <no_of_files>
  2. As citações são críticas. A definição de COMMANDé, na verdade, 2 literais de string em cada lado do arquivo $2. Isso é muito importante.
  3. Ele lista apenas as pastas que contêm os arquivos, não os arquivos em si. Para fazer o último, você teria que substituir o echo "$0"por outro find.

Agora estou testando em uma máquina Arch Linux e meu shell é 'bash', então não tenho ideia se isso funcionará no OSX, já que todos os shells NÃO são criados iguais. :-)


PRIMEIRA TENTATIVA ANTES:

Hummm. Não sei o quão semelhante o OSX é ao Unix/Linux, mas vou tentar.

Acredito que a resposta para ambas as suas perguntas esteja no primeiro teste do comando 'sh -c'. Essa é a parte que diz:

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

Para passar um segundo parâmetro para o seu script para o número de arquivos, você poderá apenas alterar o '1' para $2, então o teste seria:

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

Não coloque aspas $2porque caso contrário ele será interpretado como o segundo parâmetro passado para o comando 'sh -c', não para o seu script.

A linha de comando seria então:

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

Para atingir seu segundo requisito, pelo que entendi, você precisa colocar o egrepcomando nesse primeiro teste, assim:

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

Você pode ter que observar as citações, no entanto.

De qualquer forma, experimente e nos avise.

Responder3

Você poderia implementar isso em Python fazendo algo assim:

#!/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])

Se você chmod +x findodd.pypuder executá-lo da mesma maneira que executa seu script atual, por exemplo:

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

informação relacionada