Esta resposta vem nas seguintes partes:
- Uso básico de
-exec
- Usando
-execem combinação comsh -c
- Usando
-exec ... {} +
- Usando
-execdir
Uso básico de -exec
A -execopçã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 findsaber 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 findcomando.
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 *.txtno diretório atual ou abaixo dele. Ele testará se a sequência helloocorre 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, catserá executado para enviar o conteúdo do arquivo para o terminal.
Cada um -exectambém age como um "teste" nos nomes de caminhos encontrados por find, exatamente como -typee -namefaz. Se o comando retornar um status de saída zero (significando "sucesso"), a próxima parte do findcomando será considerada, caso contrário, o findcomando 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
findcomando).
Usando -execem combinação comsh -c
O comando que -execpode ser executado é limitado a um utilitário externo com argumentos opcionais. -execNão é possível usar embutidos, funções, condicionais, pipelines, redirecionamentos etc. diretamente do sh -cshell , a menos que seja envolto em algo como um shell filho.
Se bashforem necessários recursos, use bash -cno lugar de sh -c.
sh -cé executado /bin/shcom um script fornecido na linha de comando, seguido por argumentos opcionais da linha de comando para esse script.
Um exemplo simples de uso sh -cpor 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 $0dentro do script e, se o shell interno emitir uma mensagem de erro, ele será prefixado com essa sequência.
O argumento applesestá disponível como $1no script, e se houvesse mais argumentos, eles estariam disponíveis como $2, $3etc. Eles também estariam disponíveis na lista "$@"(exceto pelos $0quais 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, $1seria a string text, $2seria a string txte $3seria o nome do caminho findencontrado para nós. A expansão do parâmetro ${3%.$1}pegaria o nome do caminho e removeria o sufixo .textdele.
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 frome tono 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 -execwith find. Usando findem 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 findantes mesmo de executar a primeira iteração do loop.
Veja também:
Usando -exec ... {} +
O ;no final pode ser substituído por +. Isso faz findcom 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, findvocê coletará os nomes de caminho resultantes e executará o catmaior 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, mvserá executado o menor número de vezes possível. Este último exemplo requer o GNU mvdo coreutils (que suporta a -topçã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 findvariantes, mas não uma opção padrão).
Isso funciona -execcom 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 findainda prefixará o nome de base com ./BSD findnão fará isso).
Exemplo:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
Isso moverá cada arquivo encontrado*.txt para um done-textssubdiretório pré-existente no mesmo diretório em que o arquivo foi encontrado . O arquivo também será renomeado adicionando o sufixo .donea 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-textsdiretório corretamente.
Com -execdir, algumas coisas como essas se tornam mais fáceis.
A operação correspondente usando em -execvez de -execdirteria 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 {} +