No Linux ou Windows, como encontro o caminho para todos os mp3s que são os únicos mp3 nesse diretório?

No Linux ou Windows, como encontro o caminho para todos os mp3s que são os únicos mp3 nesse diretório?

Eu tenho uma pasta Música e o conteúdo é assim:

Música
    -ArtistaA
        -Song1.mp3
        -ÁlbumA
            -Song2.mp3
            -Song2.m4a
        -ÁlbumB
            -Song1.mp3
            -Song2.mp3
            -Song3.mp3
        -ÁlbumC
            -Song1.mp3
            -Song1.jpg
            -AlgumaPasta1
                -Song1.mp3
    -ArtistaB
        -Song1.mp3
    -ArtistaC
        -Song1.mp3
        -Song2.mp3
        -Song3.mp3
        -AlgumaPasta2
            -Song1.jpg
            -Song2.jpg
        -AlgumaPasta3


Estou procurando uma saída como esta:

ArtistA/Song1.mp3
ArtistA/ÁlbumA/Song2.mp3
ArtistA/AlbumC/Song1.mp3
ArtistA/AlbumC/SomeFolder1/Song1.mp3
ArtistB/Song1.mp3



Então, não quero diretórios com apenas um arquivo, quero diretórios com apenas um mp3 - independente de subdiretórios ou outros arquivos. Também não quero diretórios vazios ou diretórios que contenham mais de um mp3.

Não me oponho a um script de shell para Linux. No entanto, não quero compilar um programa em nenhum dos sistemas operacionais.

Eu tentei: find . -type d -exec sh -c 'set -- "$0"/*.mp3; [ $# -le 1 ]' {} \; -print

mas isso me dá esta saída:

.
./ArtistaB
./ArtistaA
./ArtistA/ÁlbumA
./ArtistaA/ÁlbumC
./ArtistA/AlbumC/SomeFolder1
./ArtistC/SomeFolder2
./ArtistC/SomeFolder3

que está perto, mas não é exatamente o que eu quero. Existem duas pastas vazias e nenhum nome de arquivo.

Responder1

À primeira vista, a razão pela qual seu primeiro comando está relatando as pastas vazias (aquelas sem *.mp3arquivos) é que você está dizendo -le 1em vez de -eq 1. Parece que reportará diretórios com um arquivo MP3 ou menos. Mas mudar -le 1para -eq 1não funciona. Acontece que o problema é que, em um diretório com ≥ um arquivo MP3, directory_name/*.mp3você obtém uma lista dos nomes dos arquivos MP3, mas, em um diretório sem nenhum, permanece, literalmente, como directory_name/*.mp3. Isso pode ser corrigido informando ao shell que os curingas que não correspondem a nenhum arquivo devem simplesmente desaparecer da linha de comando:

encontrar . -tipo d -exec
        sh-c'shopt -s nullglob;definir -- "$0"/*.mp3; [ $# -eq 1 ]' {} \; -imprimir

(digitado tudo como uma linha) produz

./ArtistA
./ArtistA/AlbumA
./ArtistA/AlbumC
./ArtistA/AlbumC/SomeFolder1
./ArtistB

Mas você tem o shell contando os arquivos e está pedindo findpara fazer a impressão – e findestá olhando apenas os diretórios. Por que não deixar o shell imprimir o nome do arquivo que vê?

encontrar . -tipo d -exec
        sh -c 'shopt -s nullglob; definir -- "$0"/*.mp3; [ $# -eq 1 ]&& eco "$1"'{}\;

(ou seja, se houver um arquivo, echoo nome do primeiro ( $1)) fornecerá o que você deseja.

Responder2

Para Windows usando PowerShell, supondo que você já esteja na pasta C:\YOUR_PATH:

PS C:\YOUR_PATH> (Get-ChildItem -recurse | Where-Object {$_.name -match ".mp3"}) | Group-Object -Property Directory | Where-Object {$_.Count -eq 1} | ForEach-Object { (Get-ChildItem $_.Name -Filter "*.mp3").FullName}

Saídas:

C:\YOUR_PATH\ArtistA\Song1.mp3
C:\YOUR_PATH\ArtistA\AlbumA\Song2.mp3
C:\YOUR_PATH\ArtistA\AlbumC\Song1.mp3
C:\YOUR_PATH\ArtistA\AlbumC\SomeFolder1/Song1.mp3
C:\YOUR_PATH\ArtistB\Song1.mp3

Dividindo o comando canalizado:

Obtenha todos os arquivos e pastas do diretório atual

Get-ChildItem -recurse 

Combine apenas os arquivos com base na sua máscara

Where-Object {$_.name -match ".mp3"})

Execute o agrupamento com base na propriedade do diretório

Group-Object -Property Directory 

Selecione apenas os diretórios com 1 item

Where-Object {$_.Count -eq 1} 

Extraia o nome completo do arquivo que corresponde à sua máscara de arquivo em cada diretório

ForEach-Object { (Get-ChildItem $_.Name -Filter "*.mp3").FullName} 

Responder3

Tenho certeza de que alguém tem uma maneira melhor de fazer isso, mas esse comando bash funcionará no Linux.

for Folder in $(find . -name "*.mp3" -printf "%h\n")
do
    [[ $(ls -l $Folder/*.mp3 | wc -l) -eq "1" ]] && ls $Folder/*.mp3
done

Este script realiza um teste na pasta de cada arquivo .mp3, repetindo-se para cada correspondência no mesmo arquivo. Por exemplo, ele verifica Music/ArtistA/AlbumB 3 vezes. Dependendo de quantos arquivos mp3 você possui, isso pode levar muito tempo (você precisaria de uma biblioteca bem grande).

Nesse caso, você pode complicar um pouco mais:

for Folder in $(find . -name "*.mp3" -printf "%h\n" | awk '!x[$0]++')
    do
        [[ $(ls -l $Folder/*.mp3 | wc -l) -eq "1" ]] && ls $Folder/*.mp3
    done

O resultado é o mesmo, mas só verificará cada pasta uma vez, o que pode economizar muito tempo. O comando awk remove quaisquer nomes de pasta duplicados.

Outra observação é que os arquivos diferenciam maiúsculas de minúsculas no Linux, portanto, isso ignorará totalmente qualquer arquivo .MP3ou . .Mp3Se você quiser que corresponda a eles, altere todas as 3 instâncias de mp3para[mM][pP]3

Responder4

Aqui está uma resposta:

find Music -name "*.mp3" -exec dirname {} \; |
    sort |
    uniq -c |
        while read count parent
        do
            if [[ $count -eq 1 ]]
            then
                echo "$parent"/*.mp3
            fi
        done

informação relacionada