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.
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.
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
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"
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.
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.