Esta resposta vem nas seguintes partes:
- Uso básico de
-exec
- Usando
-exec
em combinação comsh -c
- Usando
-exec ... {} +
- Usando
-execdir
Uso básico de -exec
A -exec
opção pega um utilitário externo com argumentos opcionais como argumento e o executa.
Se a string {}
estiver presente em qualquer lugar do comando, cada instância será substituída pelo nome do caminho atualmente sendo processado (por exemplo ./some/path/FILENAME
). Na maioria dos shells, os dois caracteres {}
não precisam ser citados.
O comando precisa ser finalizado com um ;
para find
saber onde termina (pois pode haver outras opções posteriormente). Para proteger o ;
shell, ele precisa ser citado como \;
ou ';'
, caso contrário, o shell o verá como o final do find
comando.
Exemplo ( \
no final das duas primeiras linhas são apenas para continuações de linha):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
Ele encontrará todos os arquivos regulares ( -type f
) cujos nomes correspondem ao padrão *.txt
no diretório atual ou abaixo dele. Ele testará se a sequência hello
ocorre em qualquer um dos arquivos encontrados usando grep -q
(o que não produz nenhuma saída, apenas um status de saída). Para os arquivos que contêm a string, cat
será executado para enviar o conteúdo do arquivo para o terminal.
Cada um -exec
também age como um "teste" nos nomes de caminhos encontrados por find
, exatamente como -type
e -name
faz. Se o comando retornar um status de saída zero (significando "sucesso"), a próxima parte do find
comando será considerada, caso contrário, o find
comando continuará com o próximo nome do caminho. Isso é usado no exemplo acima para encontrar arquivos que contêm a sequência hello
, mas para ignorar todos os outros arquivos.
O exemplo acima ilustra os dois casos de uso mais comuns de -exec
:
- Como um teste para restringir ainda mais a pesquisa.
- Executar algum tipo de ação no nome do caminho encontrado (geralmente, mas não necessariamente, no final do
find
comando).
Usando -exec
em combinação comsh -c
O comando que -exec
pode ser executado é limitado a um utilitário externo com argumentos opcionais. -exec
Não é possível usar embutidos, funções, condicionais, pipelines, redirecionamentos etc. diretamente do sh -c
shell , a menos que seja envolto em algo como um shell filho.
Se bash
forem necessários recursos, use bash -c
no lugar de sh -c
.
sh -c
é executado /bin/sh
com um script fornecido na linha de comando, seguido por argumentos opcionais da linha de comando para esse script.
Um exemplo simples de uso sh -c
por si só, sem find
:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
Isso passa dois argumentos para o script do shell filho:
A cadeia sh
. Isso estará disponível como $0
dentro do script e, se o shell interno emitir uma mensagem de erro, ele será prefixado com essa sequência.
O argumento apples
está disponível como $1
no script, e se houvesse mais argumentos, eles estariam disponíveis como $2
, $3
etc. Eles também estariam disponíveis na lista "$@"
(exceto pelos $0
quais não faria parte "$@"
).
Isso é útil em combinação com -exec
, pois permite criar scripts arbitrariamente complexos que atuam nos nomes de caminho encontrados por find
.
Exemplo: encontre todos os arquivos regulares que possuem um determinado sufixo de nome de arquivo e altere esse sufixo para outro sufixo, onde os sufixos são mantidos nas variáveis:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'
Dentro do script interno, $1
seria a string text
, $2
seria a string txt
e $3
seria o nome do caminho find
encontrado para nós. A expansão do parâmetro ${3%.$1}
pegaria o nome do caminho e removeria o sufixo .text
dele.
Ou, usando dirname
/ basename
:
find . -type f -name "*.$from" -exec sh -c '
mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'
ou, com variáveis adicionadas no script interno:
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2; pathname=$3
mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'
Observe que nesta última variação, as variáveis from
e to
no shell filho são distintas das variáveis com os mesmos nomes no script externo.
A descrição acima é a maneira correta de chamar um script complexo arbitrário de -exec
with find
. Usando find
em um loop como
for pathname in $( find ... ); do
é propenso a erros e deselegante (opinião pessoal). Ele está dividindo nomes de arquivos em espaços em branco, invocando o globbing do nome de arquivo e também força o shell a expandir o resultado completo find
antes mesmo de executar a primeira iteração do loop.
Veja também:
Usando -exec ... {} +
O ;
no final pode ser substituído por +
. Isso faz find
com que o comando fornecido seja executado com o maior número possível de argumentos (nomes de caminhos encontrados), em vez de uma vez para cada nome de caminho encontrado. A string {}
deve ocorrer imediatamente antes do +
para que isso funcione .
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
Aqui, find
você coletará os nomes de caminho resultantes e executará o cat
maior número possível de uma vez.
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
Da mesma forma aqui, mv
será executado o menor número de vezes possível. Este último exemplo requer o GNU mv
do coreutils (que suporta a -t
opção).
O uso -exec sh -c ... {} +
também é uma maneira eficiente de fazer um loop sobre um conjunto de nomes de caminho com um script arbitrariamente complexo.
O básico é o mesmo que quando usado -exec sh -c ... {} ';'
, mas o script agora leva uma lista muito maior de argumentos. Eles podem ser repetidos repetidamente "$@"
dentro do script.
Nosso exemplo da última seção que altera os sufixos do nome do arquivo:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2
shift 2 # remove the first two arguments from the list
# because in this case these are *not* pathnames
# given to us by find
for pathname do # or: for pathname in "$@"; do
mv "$pathname" "${pathname%.$from}.$to"
done' sh "$from" "$to" {} +
Usando -execdir
Também existe -execdir
(implementado pela maioria das find
variantes, mas não uma opção padrão).
Isso funciona -exec
com a diferença de que o comando shell fornecido é executado com o diretório do nome do caminho encontrado como seu diretório de trabalho atual e que {}
conterá o nome de base do nome de caminho encontrado sem o caminho (mas o GNU find
ainda prefixará o nome de base com ./
BSD find
não fará isso).
Exemplo:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Isso moverá cada arquivo encontrado*.txt
para um done-texts
subdiretório pré-existente no mesmo diretório em que o arquivo foi encontrado . O arquivo também será renomeado adicionando o sufixo .done
a ele.
Isso seria um pouco mais complicado -exec
, já que precisaríamos obter o nome de base do arquivo encontrado {}
para formar o novo nome do arquivo. Também precisamos do nome do diretório de {}
para localizar o done-texts
diretório corretamente.
Com -execdir
, algumas coisas como essas se tornam mais fáceis.
A operação correspondente usando em -exec
vez de -execdir
teria que empregar um shell filho:
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
done' sh {} +
ou,
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "${name%/*}/done-texts/${name##*/}.done"
done' sh {} +