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:
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
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 uniq
salto 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 uniq
parte -parte e o lado direito do tubo em uma única awk
linha.
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:
- Você estava usando
mindepth
em vez demaxdepth
. - Os pontos (.) no seu egrep corresponderiam a qualquer caractere. Então
.wma
teria correspondido a 'Snowman.txt'. - 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:
- O uso é:
findodd.sh <top_folder> <no_of_files>
- 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. - 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 outrofind
.
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 $2
porque 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 egrep
comando 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.py
puder executá-lo da mesma maneira que executa seu script atual, por exemplo:
./findodd.py 1 /Users/paul/Music