Resumo:
A menos que você esteja muito mais familiarizado com xargs do que -exec , provavelmente desejará usar -exec quando usar find .
Como xargs é um programa separado, chamá-lo provavelmente será um pouco menos eficiente do que usar -exec , que é um recurso do programa find . Geralmente, não queremos chamar um programa extra se ele não fornecer nenhum benefício adicional em termos de confiabilidade, desempenho ou legibilidade. Como find ... -exec ... fornece a capacidade de executar comandos com uma lista de argumentos (como xargs faz), se possível, não há realmente nenhuma vantagem de usar xargs com find over -exec . No caso de ffmpeg , temos que especificar arquivos de entrada e saída, portanto não podemos obter ganhos de desempenho usando qualquer método para construir uma lista de argumentos, e com xargs remover a extensão de nome de arquivo original ilógica é mais difícil. / p>
O que xargs faz
Observação: O sinalizador detalhado (que imprime o comando construído com seus argumentos) em xargs é -t e o sinalizador interativo (que faz com que o usuário seja solicitado confirmação para operar em cada argumento) é -p . Você pode achar ambos úteis para entender e testar seu comportamento.
xargs tenta transformar seu STDIN (normalmente o STDOUT do comando anterior que foi enviado para ele) em uma lista de argumentos para algum comando.
command1 | xargs command2 [output of command1 will be appended here]
Como STDOUT ou STDIN é apenas um fluxo de texto (também é por isso que você não deve analisar a saída de ls ), xargs é facilmente desativado. Ele lê argumentos como sendo delimitados por espaços ou novas linhas. Os nomes de arquivos podem conter espaços e podem até conter novas linhas, e esses nomes de arquivos causarão um comportamento inesperado. Digamos que você tenha um arquivo chamado foo bar . Quando uma lista contendo esse nome de arquivo é canalizada para xargs , ele tenta executar o comando especificado em foo e em bar .
O mesmo problema ocorre quando você digita command foo bar , e você sabe que pode evitá-lo citando o espaço ou o nome inteiro, por exemplo, command foo\ bar ou command "foo bar" , mas mesmo se pudermos citar a lista passada para xargs geralmente não queremos, porque não queremos que toda a lista seja tratada como um único argumento. A solução padrão para isso é usar o caractere nulo como delimitador, já que os nomes de arquivos não podem conter:
find path test(s) -print0 | xargs -0 command
Isso faz com que find anexe o caractere nulo a cada nome de arquivo em vez de um espaço e xargs para tratar apenas o caractere nulo como delimitador.
Ainda podem ocorrer problemas se o comando não aceitar vários argumentos ou se a lista de argumentos for extremamente longa.
Nesse caso, você está usando ffmpeg , que espera que os arquivos de entrada sejam especificados primeiro e que os arquivos de saída sejam especificados por último. Podemos dizer a ffmpeg quais arquivos (s) usar como entrada explicitamente com o -i flag, mas precisamos dar o nome do arquivo de saída (do qual o formato normalmente é adivinhado, embora também possamos especificá-lo). Portanto, para construir comandos adequados, você precisa usar a opção replace string ( -I ou -i ) de xargs para especificar os arquivos de entrada e saída:
... | xargs -I{} command {} {}.out
(a documentação diz que -i está obsoleto para essa finalidade e devemos usar -I , mas não tenho certeza do porquê. Ao usar -I , você deve especificar a substituição ( {} é normalmente usado ) imediatamente após a opção. Com -i você pode omitir para especificar a substituição, mas {} é entendido por padrão.)
A opção -I faz com que a lista de comandos seja dividida apenas em novas linhas, não em espaços, portanto, se você tiver certeza de que seus nomes não conterão novas linhas, não será necessário usar -print0 | xargs -0 quando usar -I . Se você não tiver certeza, ainda poderá usar a sintaxe mais segura:
find -name "*.flac" -print0 | xargs -0I{} ffmpeg -i {} {}.mp3
No entanto, o benefício de desempenho de xargs (que permite executar um comando uma vez com uma lista de argumentos) é perdido aqui, pois ffmpeg deve ser executado uma vez para cada par de arquivos de entrada e saída veja isso facilmente prefixando echo to ffmpeg para testar o comando acima). Isso também produz um nome de arquivo ilógico e não permite que você execute vários comandos. Para fazer o último, você pode chamar bash , como na resposta da sobremesa :
... | xargs -I{} bash -c 'ffmpeg -i {} {}.mp3 && rm {}'
mas renomear é complicado .
Como -exec é diferente
Quando você usa a opção -exec para find , os arquivos encontrados são transmitidos como argumentos para o comando após -exec . Eles não são transformados em texto. Com a sintaxe:
find ... -exec command {} \;
command é executado uma vez para cada arquivo encontrado. Com a sintaxe
find ... -exec command {} +
uma lista de argumentos é construída a partir dos arquivos encontrados para que possamos executar o comando apenas uma vez (ou somente quantas vezes forem necessárias) em vários arquivos, fornecendo o benefício de desempenho fornecido por xargs .No entanto, como os argumentos de nome de arquivo não são construídos a partir de um fluxo de texto, o uso de -exec não tem o problema xargs tem de quebrar espaços e outros caracteres especiais.
Com ffmpeg , não podemos usar + pela mesma razão que xargs não ofereceu nenhum benefício de desempenho; já que precisamos especificar entrada e saída, o comando deve ser executado em cada arquivo individualmente. Nós temos que usar alguma forma de
find -name "*.flac" -exec ffmpeg -i {} {}.out \;
Isso, mais uma vez, fornecerá a você um arquivo com nomes pouco ilógicos, como explica a resposta da sobremesa , então talvez você queira tira-lo, como resposta da sobremesa explica como fazer com manipulação de string (não é fácil feito em xargs ; outra razão para usar -exec ). Ele também explica como executar vários comandos no arquivo para que você possa remover com segurança o arquivo original após uma conversão bem-sucedida.
Em vez de repetir a recomendação de sobremesa, com a qual concordo, sugiro uma alternativa a find , que permite flexibilidade semelhante à execução de bash -c após -exec ; um loop bash for :
shopt -s globstar # allow recursive globbing with **
for f in ./**/*.flac; do # for all files ending with .flac
# convert them, stripping the original extension from the new filename
echo ffmpeg -i "$f" "${f%.flac}.mp3" &&
echo rm -v "$f" # if that succeeded, delete the original
done
shopt -u globstar # turn recursive globbing off
Remova os echo es após o teste para operar nos arquivos.
ffmpeg não reconhece -- para marcar o fim das opções, portanto, para evitar nomes de arquivos que começam com - sendo interpretados como opções, usamos ./ para indicar o diretório atual em vez de começar com ** , para que todos os caminhos comecem com ./ em vez de nomes de arquivos arbitrários. Isso significa que não precisamos usar -- com rm (que também o reconhece).
Observação : você deve citar sua expressão -name test se ela contiver caracteres curinga, caso contrário, o shell os expandirá, se possível (ou seja, se eles corresponderem a qualquer arquivo no diretório atual) antes deles são passados para find , então, em primeiro lugar, use
find -name "*.flac"
para evitar comportamentos inesperados.