find -exec: adiciona argumentos entre {} e +

Oct 21 2020

Quero usar find para localizar arquivos e copiá-los para um diretório, então tentei:

find . -name '*.png' -exec cp {} /tmp/dest +

No entanto, isso falha com

find: missing argument to `-exec'

Quando substituo o +por a, ;ele funciona, mas invoca cp para cada arquivo individualmente. Como posso adicionar um argumento final (como um diretório de destino) ao usar a +forma de -exec?

Claro, neste caso eu posso contornar a limitação aparente usando cp -t(como indicado neste post no Stack Overflow , mas essa solução é específica para cp. Em vez disso, posso estar usando rsync, scp ou alguma outra ferramenta. Existe uma maneira geral de adicionar argumentos entre {}e +em find -exec?

Respostas

12 Kusalananda Oct 21 2020 at 19:22

Não, se você estiver usando -exec ... {} +, pode haver nada entre {}e +para além do espaço em branco. Não há jeito de contornar isso.

A partir da especificação padrão POSIX do findcomando :

-exec utility_name [argument ...] ;
-exec utility_name [argument ...] {} +

O final da expressão primária deve ser pontuado por a <semicolon>ou por a <plus-sign>. Apenas um <plus-sign>que segue imediatamente um argumento contendo apenas os dois caracteres {}deve pontuar o final da expressão primária. Outros usos do <plus-sign>não devem ser tratados como especiais.

Uma solução mais genérica possivelmente seria

find ... -exec sh -c 'cp "$@" /tmp/dest' sh {} +

Aqui, um sh -cscript embutido é executado com lotes de argumentos de find. Dentro do script embutido, "$@"estará a lista de argumentos passados ​​(citados individualmente), o que nos permite colocá-los como queremos na cplinha de comando.

Isso nos permite usar não GNU cp(por exemplo, no macOS ou em outros sistemas BSD, onde não há -topção) ou qualquer outro utilitário de onde se queira adicionar outros argumentos ao final da lista de nomes de caminho vindos find.

Relacionado:

  • Compreendendo a opção -exec de `find`

Ninguém pediu isso, mas enfim ...

Com o diretório de destino em uma variável destdir:

destdir=/tmp/dest

find ... -exec sh -c 'destdir=$1; shift; cp "$@" "$destdir"' sh "$destdir" {} +

Observe que destdirna chamada do shell findé uma variável separada do destdirno sh -cscript.

Ou, com bash:

destdir=/tmp/dest

find ... -exec bash -c 'cp "${@:2}" "$1"' bash "$destdir" {} +

Isso é "fatiar" a "$@"lista para reorganizá-la apropriadamente para a cplinha de comando, sem extrair $1, o nome do caminho do diretório de destino, em uma variável separada.