Usando ponto e vírgula (;) vs mais (+) com exec em find

159

Por que há uma diferença na saída entre usar

find . -exec ls '{}' \+

e

find . -exec ls '{}' \;

Eu tenho:

$ find . -exec ls  \{\} \+
./file1  ./file2

.:
file1  file2  testdir1

./testdir1:
testdir2

./testdir1/testdir2:


$ find . -exec ls  \{\} \;
file1  file2  testdir1
testdir2
./file2
./file1
Ankur Agarwal
fonte

Respostas:

249

Isso pode ser melhor ilustrado com um exemplo. Digamos que findapareça esses arquivos:

file1
file2
file3

Usando -execcom ponto-e-vírgula ( find . -exec ls '{}' \;), executará

ls file1
ls file2
ls file3

Mas se você usar um sinal de adição ( find . -exec ls '{}' \+), o maior número possível de nomes de arquivos será passado como argumento para um único comando:

ls file1 file2 file3

O número de nomes de arquivos é limitado apenas pelo comprimento máximo da linha de comando do sistema. Se o comando exceder esse comprimento, o comando será chamado várias vezes.

Martin
fonte
1
obrigado. isto é muito útil para querer classificar os arquivos resultantes: encontrar -maxdepth 1 do tipo f -mtime -1 -exec ls -ltr {} \ +
FBAS
1
Q mudo: Percebo que +associado a -execsempre é escapado, mas +associado a -mtimenão é. você sabe a razão? Eu acho que é hábito de escapar ;associado -exec.
Kevinarpe 10/05
3
@kevinarpe de fato, eu gostaria que fosse um hábito ;. Não pode imaginar nunca ser necessário escapar+
Martin
36

Todas as respostas até agora estão corretas. Ofereço isso como uma demonstração mais clara (para mim) do comportamento descrito usando, em echovez de ls:

Com um ponto e vírgula, o comando echoé chamado uma vez por arquivo (ou outro objeto do sistema de arquivos) encontrado:

$ find . -name 'test*' -exec echo {} \;
./test.c
./test.cpp
./test.new
./test.php
./test.py
./test.sh

Com um sinal de adição, o comando echoé chamado apenas uma vez. Todo arquivo encontrado é passado como argumento.

$ find . -name 'test*' -exec echo {} \+
./test.c ./test.cpp ./test.new ./test.php ./test.py ./test.sh

Se findaparecer um grande número de resultados, você pode achar que o comando que está sendo chamado bloqueia o número de argumentos.

Johnsyweb
fonte
1
Você não deve adicionar os resultados apenas a um número que torne seguro transmiti-los ao shell? Pelo menos é o que xargsfaz ... em princípio, nunca se engasga com muitos argumentos.
Rmano
1
@Rmano: Eu já vi find(e xargs) no Solaris emitir mais argumentos do que poderia ser consumido. O xargs(e find) nos findutils do GNU` parecem se comportar de maneira mais sensata, mas nem todos usam o GNU.
Johnsyweb
2
@ Johnsyweb, todos os POSIX findtentariam evitar atingir o limite do número de argumentos. E isso inclui o Solaris (pelo menos 10). Onde pode falhar é se você fizer algo como find ... -exec ksh -c 'cmd "$@" "$@"' sh {} +ou find ... -exec ksh -c 'files="$*" cmd "$@"' sh {} +, mas findrealmente não pode ser responsabilizado por isso. Observe que o GNU findfoi uma das últimas findimplementações a oferecer suporte +(costumava ser um problema para portar scripts para sistemas GNU).
Stephane Chazelas
18

De man find:

comando -exec;

Executar 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 em que está sozinho, como em algumas versões do find. Ambas as construções podem precisar ser escapadas (com um '\') ou citadas para protegê-las da expansão pelo shell. Veja a seção EXEMPLES sec para exemplos de 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 opção -exec;

comando -exec {}

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

Portanto, pelo que entendi, \;executa um comando separado para cada arquivo encontrado por find, enquanto \+anexa os arquivos e executa um único comando em todos eles. O \é um caractere de escape, então é:

ls testdir1; ls testdir2 

vs

ls testdir1 testdir2

Fazer o acima no meu shell espelhava a saída na sua pergunta.

exemplo de quando você gostaria de usar \+

Suponha dois arquivos 1.tmpe 2.tmp:

1.tmp:

1
2
3

2.tmp:

0
2
3

Com \;:

 find *.tmp -exec diff {} \;
> diff: missing operand after `1.tmp'
> diff: Try `diff --help' for more information.
> diff: missing operand after `2.tmp'
> diff: Try `diff --help' for more information.

Considerando que se você usar \+(para concatenar os resultados de find):

find *.tmp -exec diff {} \+
1c1,3
< 1
---
> 0
> 2
> 30

Portanto, neste caso, é a diferença entre diff 1.tmp; diff 2.tmpediff 1.tmp 2.tmp

Há casos em que \;é apropriado e \+será necessário. O uso de \+with rmé uma instância em que, se você estiver removendo um grande número de arquivos, o desempenho (velocidade) será superior \;.

matchew
fonte
Também posso ler a página do manual. E compreendi, mas acho que não entendo a diferença entre usar;
Ankur Agarwal
não acho que o -1 fosse justo, expliquei minha compreensão do homem. Eu não apenas copiei o homem e fui embora. mas editei minha resposta para incluir um exemplo melhor.
matchew 22/05
10

findtem sintaxe especial. Você usa o {}que é porque eles têm significado para encontrar como o nome do caminho do arquivo encontrado e (a maioria) os shells não os interpretam de outra maneira. Você precisa da barra invertida \;porque o ponto e vírgula tem significado para o shell, que o consome antes que findpossa obtê-lo. Então, o que você findquer ver APÓS o shell ser feito, na lista de argumentos passada para o programa C, é

"-exec", "rm", "{}", ";"

mas você precisa \;na linha de comando para obter um ponto-e-vírgula através do shell para os argumentos.

Você pode se safar \{\}porque a interpretação de shell-quote \{\}é justa {}. Da mesma forma, você pode usar '{}'.

O que você não pode fazer é usar

 -exec 'rm {} ;'

porque o shell interpreta isso como um argumento,

"-exec", "rm {};"

e rm {} ;não é o nome de um comando. (Pelo menos, a menos que alguém esteja realmente brincando.)

Atualizar

a diferença é entre

$ ls file1
$ ls file2

e

$ ls file1 file2

O +está catenando os nomes em uma linha de comando.

Charlie Martin
fonte
1
Eu entendo o que você está dizendo. Estou perguntando qual é a diferença entre usar;
Ankur Agarwal
1
desculpe, mas você leu minha pergunta ou meu comentário com cuidado? Pode ser que eu precise reformulá-lo. Por que há um op / p diferente quando uso ponto-e-vírgula com exec em find versus quando uso plus com exec em find?
Ankur Agarwal
2
Esta é uma excelente explicação para POR QUE o comando é assim, que a resposta aceita não cobre. Obrigado!
Sherwin Yu
1

A diferença entre ;(ponto e vírgula) ou +(sinal de mais) é como os argumentos são passados ​​para o parâmetro -exec/ localização -execdir. Por exemplo:

  • using ;executa vários comandos (separadamente para cada argumento),

    Exemplo:

    $ find /etc/rc* -exec echo Arg: {} ';'
    Arg: /etc/rc.common
    Arg: /etc/rc.common~previous
    Arg: /etc/rc.local
    Arg: /etc/rc.netboot
    

    Todos os argumentos a seguir findsão considerados argumentos para o comando.

    A sequência {}é substituída pelo nome do arquivo atual que está sendo processado.

  • using +executa os comandos menos possíveis (como os argumentos são combinados). É muito semelhante à forma como o xargscomando funciona, portanto, ele usará o maior número possível de argumentos por comando para evitar exceder o limite máximo de argumentos por linha.

    Exemplo:

    $ find /etc/rc* -exec echo Arg: {} '+'
    Arg: /etc/rc.common /etc/rc.common~previous /etc/rc.local /etc/rc.netboot
    

    A linha de comando é criada anexando cada nome de arquivo selecionado no final.

    Apenas uma instância de {}é permitida dentro do comando.

Veja também:

kenorb
fonte
-5

estávamos tentando encontrar arquivos para tarefas domésticas.

encontrar . -exec echo {} \; comando correu durante a noite no final sem resultado.

encontrar . -exec eco {} \ + tem resultados e levou apenas algumas horas.

Espero que isto ajude.

Ron
fonte
2
Essa resposta não explica como essas duas maneiras funcionam e como os resultados produzidos por elas diferem.
misko321