como passar o resultado de `find` como uma lista de arquivos?

11

A situação é que eu tenho um MP3 player mpg321que aceita uma lista de arquivos como argumento. Eu mantenho minha música em um diretório chamado "music", no qual existem mais alguns diretórios. Eu só quero jogar todos eles, então eu corro o programa com

mpg321 $(find /music -iname "*\.mp3")

. O problema é que alguns nomes de arquivos têm espaços em branco, e o programa divide esses nomes em partes menores e reclama de arquivos ausentes. Agrupando o resultado entre findaspas

mpg321 "$(find /music -iname "*\.mp3")"

não ajuda porque tudo se tornará um "nome de arquivo" grande, o que obviamente não foi encontrado.

Como posso fazer isso então? Se isso importa, estou usando bash, mas mudarei para zshbreve.

phunehehe
fonte

Respostas:

17

Tente usar o find -print0ou a -printfopção em combinação com o xargsseguinte:

find /music -iname "*\.mp3" -print0 | xargs -0 mpg321

Como isso funciona é explicado pela página de manual do find :

-print0

Verdade; imprima o nome completo do arquivo na saída padrão, seguido por um caractere nulo (em vez do caractere de nova linha que -print usa). Isso permite que nomes de arquivos que contenham novas linhas ou outros tipos de espaço em branco sejam corretamente interpretados por programas que processam a saída de localização. Esta opção corresponde à opção -0 do xargs.

Steven D
fonte
Desculpe por toda a edição. Eu me lembro do padrão -print0 xarg -0 falhando em outros tipos de espaço em branco, mas não consegui encontrar um exemplo.
Steven D
ah sim isso funciona! mas ainda me pergunto por mpg321 $(find /music -iname "*\.mp3" -print0)que não?
Phreinhe # 1/10
1
Bem, acredito que isso não funcione por dois motivos: (1) os nomes dos arquivos são separados por caracteres nulos, o que não é o que o mpg321 está esperando e (2) o xargs está fazendo o trabalho de escapar do outro espaço em branco, não encontrar.
Steven D
2
@phunehehe, @Steven: mpg321não tem nada a ver com isso, é o shell que está dividindo a saída findem argumentos separados. E -print0 | xargs -0funcionará com todos os nomes de arquivos possíveis.
Gilles 'SO- stop being evil' em
8
find /music -iname "*\.mp3" -exec mpg123 {} +

Com o GNU find, você também pode usar -print0exargs -0 , mas há pouco sentido em aprender mais uma ferramenta. A -exec ... {} +sintaxe recebe pouca menção porque o Linux a adquiriu posteriormente -print0, mas não há razão para não usá-lo agora.

Com zsh ou bash 4, isso é muito mais simples:

mpg123 **/*.[Mm][Pp]3

Somente no zsh, você pode fazer um (parte de a) sem distinção entre maiúsculas e minúsculas:

mpg123 (#i)**/*.mp3
Gilles 'SO- parar de ser mau'
fonte
+1 a isso, "find -print0" é um truque feio.
p-static
2

Acho que a solução de Steven é a melhor, mas outra maneira é usar o -Isinalizador de xargs , que permite especificar uma string que será substituída no comando pelo argumento (em vez de apenas anexar o argumento ao final do comando). Você pode usar isso para citar o argumento:

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"
Michael Mrozek
fonte
Eu não entendo esta resposta ... Você está usando o -0sinalizador de xargs sem encontrar -print0. Além disso, a -Ibandeira de xargs tem várias consequências. Ao contrário do simples, xargsque comprimirá todas as linhas de stdin em um comando, xargs -Iserá executado mpg321potencialmente centenas ou milhares de vezes (uma vez para cada arquivo), o que certamente não é a intenção. Lembre-se também de que citar foo é desnecessário, assim como xargsisso internamente. Observe que se você compactar todos os nomes de arquivos na linha de linha e enviá-los para xargs -I, eles não serão abertos.
Seis
1

Geralmente, é melhor diretamente, -exec ${tgt_process} \{\} +mas se você precisar obter uma lista de nomes de arquivos delimitada de maneira confiável e confiável em um arquivo ou fluxo, findpor qualquer motivo, poderá fazer o seguinte:

find -exec sh -c 'printf "///%s///\n" "$@"' -- \{\} +

O que você obtém disso são duas seqüências únicas . No início de cada nome de arquivo está a string \n///e na cauda de todo nome de arquivo está a string ///\n. Essas duas seqüências de caracteres não ocorrem em nenhum outro lugar na findsaída de, exceto nessas posições, independentemente dos caracteres que os nomes de arquivos contêm.

Além disso, o uso acima é portátil POSIX básico e pode ser utilizado para trabalhar em praticamente qualquer sistema unix. Isso não se aplica ao uso de um delimitador de byte nulo - apesar de sua conveniência - recomendado por alguns outros.

Mas, novamente, isso só é necessário se você não puder diretamente o -execseu $tgt_processpor qualquer motivo, pois esse deve ser seu objetivo. Por um lado, o método acima ainda requer análise. Por exemplo, se você quisesse cada shell de nome de arquivo citado, primeiro teria que garantir que quaisquer aspas no nome do arquivo fossem escapadas:

find ... + | sed 's|'\''|&"&"&|g;s|///|'\''|g'

Isso gera uma matriz de nomes de arquivos com escape apropriado, independentemente de quais sejam seus caracteres constituintes. Agora você só precisa esperar que seu aplicativo no lado receptor não o altere.

mikeserv
fonte
0

Outra maneira de fazer isso é escapar de todos os caracteres especiais que aparecem nos nomes dos arquivos. Por exemplo:

find /music -iname "*\.mp3" | sed 's!\([] \*\$\/&[]\)!\\\1!g' | xargs mpg321

Basicamente, isso passará os nomes de arquivos com escape adequado para xargs para execução e não haverá problemas.

Priyabrata Patnaik
fonte
1
Isso ainda quebra nas novas linhas nos nomes de arquivos. E você também pode sed 's|.|\\&|g'- é o que o POSIX recomenda de qualquer maneira.
mikeserv