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.