Por que find -exec mv {} ./target/ + não funciona?

98

Eu quero saber exatamente o que {} \;e {} \+e | xargs ...fazer. Por favor, esclareça isso com explicações.

Abaixo de 3 comandos são executados e geram o mesmo resultado, mas o primeiro comando leva um pouco de tempo e o formato também é um pouco diferente.

find . -type f -exec file {} \;
find . -type f -exec file {} \+
find . -type f | xargs file

É porque o primeiro executa o filecomando para cada arquivo proveniente do findcomando. Então, basicamente funciona como:

file file1.txt
file file2.txt

Mas os 2 últimos achados com -execcomandos, execute o comando de arquivo uma vez para todos os arquivos como abaixo:

file file1.txt file2.txt

Então eu executo os seguintes comandos em que o primeiro é executado sem problemas, mas o segundo fornece uma mensagem de erro.

find . -type f -iname '*.cpp' -exec mv {} ./test/ \;
find . -type f -iname '*.cpp' -exec mv {} ./test/ \+ #gives error:find: missing argument to `-exec'

Para o comando com {} \+, aparece a mensagem de erro

find: missing argument to `-exec'

por que é que? alguém pode explicar o que estou fazendo de errado?

Shahadat Hossain
fonte
A questão real é simples: por que a primeira funciona e a segunda não? (1) encontrar. -tipo f -inome ' .cpp' -exec mv {} ./teste/ \; (2) encontrar. -tipo f -iname ' .cpp' -exec mv {} ./test/ \ +
Shahadat Hossain,

Respostas:

185

A página de manual (ou o manual GNU online ) explica praticamente tudo.

comando find -exec {} \;

Para cada resultado, command {}é executado. Todas as ocorrências de {}são substituídas pelo nome do arquivo. ;é prefixado com uma barra para evitar que o shell o interprete.

comando find -exec {} +

Cada resultado é anexado commande executado posteriormente. Levando em conta as limitações de comprimento do comando, acho que este comando pode ser executado mais vezes, com a página do manual me apoiando:

o número total de invocações do comando será muito menor do que o número de arquivos correspondentes.

Observe esta citação da página do manual:

A linha de comando é construída da mesma forma que o xargs constrói suas linhas de comando

É por isso que nenhum caractere é permitido entre {}e +exceto para espaços em branco. +faz o find detectar que os argumentos devem ser acrescentados ao comando da mesma forma que xargs.

A solução

Felizmente, a implementação GNU de mvpode aceitar o diretório de destino como um argumento, com um -tou o parâmetro mais longo --target. Seu uso será:

mv -t target file1 file2 ...

Seu findcomando se torna:

find . -type f -iname '*.cpp' -exec mv -t ./test/ {} \+

Na página do manual:

comando -exec;

Execute o comando; true se 0 status for retornado. Todos os seguintes argumentos a serem encontrados são considerados argumentos para o comando até um argumento que consiste em `; ' é encontrado. A string `{} 'é substituída pelo nome do arquivo atual sendo processado em todos os lugares em que ocorre nos argumentos do comando, não apenas nos argumentos onde está sozinho, como em algumas versões de find. Ambas as construções podem precisar de escape (com um `\ ') ou entre aspas para protegê-las da expansão pelo shell. Consulte a seção EXEMPLOS para obter exemplos do uso da opção -exec. O comando especificado é executado uma vez para cada arquivo correspondente. O comando é executado no diretório inicial. Existem problemas de segurança inevitáveis ​​em torno do uso da ação -exec; você deve usar a opção -execdir.

-exec comando {} +

Esta variante da ação -exec executa o comando especificado nos arquivos selecionados, mas a linha de comando é construída anexando cada nome de arquivo selecionado no final; o número total de invocações do comando será muito menor do que o número de arquivos correspondentes. A linha de comando é construída da mesma maneira que xargs constrói suas linhas de comando. Apenas uma instância de `{} 'é permitida no comando. O comando é executado no diretório inicial.

Lekensteyn
fonte
1
Sei realmente como funciona, já li este manual várias vezes, mas recebo uma mensagem de erro por usar {} +, embora funcione para {} \; e estou usando o Cygwin no windows.
Shahadat Hossain,
1
@Shahadat: você leu a parte anterior "Da página de manual"? Você colocou ./test/entre {}e +, mas nenhum caractere diferente de espaço em branco é permitido entre eles.
Lekensteyn,
ru dizendo que eu não devo colocar ./test/ entre {} e +. Então, como o comando mv funcionará; mv precisa da origem que é {} e do destino que é ./test/ e termina com +. você pode escrever o comando o que você acha certo?
Shahadat Hossain,
@Shahadat: Vejo o que você está tentando alcançar. O Windows é lento na execução de programas, então você deseja combiná-lo em um comando. Vou adicionar uma alternativa para a resposta.
Lekensteyn,
1
O +comando é um pouco estranho AFAIU, uma vez que cola os arquivos "no final" (e não no lugar de {}), então por que usar {}- isso é confuso. Obrigado pela -topção que eu não conhecia, parece que essa opção foi criada como uma solução alternativa para esse -exec +problema!
e2-e4
6

Encontrei o mesmo problema no Mac OSX , usando um shell ZSH : neste caso não há -topção para mv, então tive que encontrar outra solução. No entanto, o seguinte comando foi bem-sucedido:

find .* * -maxdepth 0 -not -path '.git' -not -path '.backup' -exec mv '{}' .backup \;

O segredo era citar o aparelho . Não há necessidade de as chaves estarem no final do execcomando.

Eu testei no Ubuntu 14.04 (com shell BASH e ZSH ), funciona da mesma forma.

No entanto, ao usar o +sinal, parece de fato que tem que estar no final do execcomando.

arvymetal
fonte
{}precisa ser citado nos shells fishe rc, mas não em zsh, bashnem em quaisquer outros shells das famílias Bourne ou csh.
Stephane Chazelas
@StephaneChazelas Sim, testado novamente em um Ubuntu com bash, de fato as aspas não são necessárias. Curiosamente, tive um problema ao não citá-los no MacOS (usando zsh). Mas não tenho um Mac disponível para tentar de novo ...
arvymetal
3

O equivalente padrão de find -iname ... -exec mv -t dest {} +para findimplementações que não oferecem suporte -inameou mvimplementações que não oferecem suporte -té usar um shell para reordenar os argumentos:

find . -name '*.[cC][pP][pP]' -type f -exec sh -c '
  exec mv "$@" /dest/dir/' sh {} +

Ao usar -name '*.[cC][pP][pP]', também evitamos depender da localidade atual para decidir qual é a versão em maiúsculas de cou p.

Observe que +, ao contrário de ;não é especial em nenhum shell, portanto, não precisa ser citado (embora citar não prejudique, exceto, é claro, que shells como rcesse não suportam \como um operador de citação).

A fuga /em /dest/dir/é para que mvfalha com um erro em vez de renomear foo.cpppara /dest/dirno caso em que apenas um cpparquivo foi encontrado e /dest/dirnão existia ou não era um diretório (ou link simbólico para o diretório).

Stephane Chazelas
fonte
+1 ... operação em shell como preliminar para executar um comando é realmente útil para uma variedade de casos de uso ... bom.
Cbhihe
0
find . -name "*.mp3" -exec mv --target-directory=/home/d0k/Музика/ {} \+
DarkLabs
fonte
Adicione alguma explicação à sua resposta para que outros possam aprender com ela
Nico Haase
Você precisa responder à pergunta, que pede explicação. Somente código não é uma resposta.
Lajos Arpad,
-1

não, a diferença entre +e \;deve ser revertida. +anexa os arquivos ao final do comando exec, em seguida, executa o comando exec e \;executa o comando para cada arquivo.

O problema é find . -type f -iname '*.cpp' -exec mv {} ./test/ \+que find . -type f -iname '*.cpp' -exec mv {} ./test/ + não há necessidade de escapar ou encerrar o+

xargs que não uso há muito tempo, mas acho que funciona como +.

Mike Ramirez
fonte
Também tentei fazer isso, mas recebi a mesma mensagem de erro. Além disso, em todos os lugares eu achei que só uso +, mas no meu cygwin eu tenho que usar \ + ou "+" para funcionar.
Shahadat Hossain,
oh este é um ambiente cygwin. Desculpe, então eu não sei, eu não uso o shell cygwin, apenas uso um * nix.
Mike Ramirez,
1
@Shahadat Hossain tente -name "*.cpp"Eu dificilmente uso -iname a menos que eu queira fazer alguma pesquisa regex difícil, como -iname '??? trabalho. * \. Cpp'
Mike Ramirez
1
@Mike: Acho que você entendeu mal a diferença entre -inamee -name. -inameé a versão -nameque não diferencia maiúsculas de minúsculas e não tem diferenças no tratamento das expressões regulares. Eu sugiro tentar comandos antes de postar, seu comando falha no meu shell também.
Lekensteyn,
1
@Lekensteyn Já foi estabelecido que era o caso antes de seu comentário. Pensei ter reconhecido Shahadat antes de sua postagem, foi um simples "ok". Não, eu não executei manualmente, eu fiz isso do topo da minha cabeça e raramente uso essa forma de pesquisa regex com find. Era apenas uma coisa do tipo 'pode ajudar'.
Mike Ramirez,