web-development-kb-pt.site

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

A situação é, eu tenho um MP3 player mpg321 que 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 executo o programa com

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

. O problema é que alguns nomes de arquivo têm um espaço em branco e o programa divide esses nomes em partes menores e reclama da falta de arquivos. Envolvendo o resultado de find entre aspas

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

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

Como posso fazer isso então? Se isso for importante, estou usando bash, mas mudarei para zsh em breve.

11
phunehehe

Tente usar find's -print0 ou -printf opção em combinação com xargs assim:

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

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

-print0

Verdade; imprime 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 arquivo que contenham novas linhas ou outros tipos de espaço em branco sejam interpretados corretamente por programas que processam a saída de localização. Esta opção corresponde à opção -0 de xargs.

17
Steven D
find /music -iname "*\.mp3" -exec mpg123 {} +

Com GNU find, você também pode usar -print0 e xargs -0 , mas não adianta aprender outra ferramenta. O -exec ... {} + a sintaxe recebe pouca menção porque o Linux a adquiriu depois de -print0, mas não há motivo para não usá-lo agora.

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

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

Apenas no zsh, você pode tornar um (parte de um) padrão sem distinção entre maiúsculas e minúsculas:

mpg123 (#i)**/*.mp3
8

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

find /music -iname "*\.mp3" | xargs -0 -Ifoo mpg321 "foo"
2
Michael Mrozek

Normalmente, é melhor -exec ${tgt_process} \{\} + mas se você fizer precisa para obter uma lista delimitada de forma confiável de nomes de arquivos em um arquivo ou fluxo de find por qualquer motivo, você pode fazer isso:

find -exec sh -c 'printf "///%s///\n" "[email protected]"' -- \{\} +

O que você ganha com isso são duas únicas strings. No cabeçalho de cada nome de arquivo está a string \n/// e no final de cada nome de arquivo está a string ///\n. Essas duas strings não ocorrem em nenhum outro lugar na saída de find, exceto nessas posições, independentemente de quaisquer caracteres que os nomes de arquivo contenham.

Além disso, o uso acima é a linha de base POSIX portátil e pode funcionar em quase qualquer sistema Unix. Isso não é verdade para o 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 -exec seu $tgt_process por qualquer motivo, pois esse deve ser o seu objetivo. Por um lado, o método acima ainda requer análise. Por exemplo, se você quiser que cada nome de arquivo Shell seja citado, primeiro você terá que garantir que todas as aspas no nome do arquivo sejam escapadas:

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

Isso gera uma matriz de nomes de arquivos com escape de Shell adequado, independentemente de quaisquer que sejam seus caracteres constituintes. Agora você só tem que esperar que seu aplicativo na extremidade receptora não o estrague.

1
mikeserv

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

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

Isso basicamente passará os nomes de arquivo com escape adequado para xargs para execução e não haverá problemas.

0
Priyabrata Patnaik