Eu me encontro constantemente procurando a sintaxe de
find . -name "FILENAME" -exec rm {} \;
principalmente porque eu não vejo exatamente como a parte -exec
funciona. Qual é o significado das chaves, da barra invertida e do ponto-e-vírgula? Existem outros casos de uso para aquela sintaxe?
Comentários
Resposta
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 opção -exec
usa um utilitário externo com argumentos opcionais como seu argumento e o executa.
Se a string {}
estiver presente em qualquer lugar no comando fornecido, cada instância dela 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 encerrado com um ;
para find
saber onde termina (pois pode haver outras opções posteriormente s). Para proteger o ;
do shell, ele precisa ser citado como \;
ou ";"
, caso contrário, o shell o verá como o final do comando find
.
Exemplo (o \
no o final das duas primeiras linhas são apenas para continuações de linha):
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} ";"
Isso encontrará todos os arquivos regulares (-type f
) cujos nomes correspondem ao padrão *.txt
no diretório atual ou abaixo dele. Em seguida, testará se a string hello
ocorre em qualquer um dos arquivos encontrados usando grep -q
(que não produz nenhuma saída, apenas uma saída status). Para os arquivos que contêm a string, cat
será executado para enviar o conteúdo do arquivo para o terminal.
Cada -exec
também atua como um “teste” nos nomes de caminho encontrados por find
, assim como -type
e -name
sim. Se o comando retornar um status de saída zero (significando “sucesso”), a próxima parte do comando find
é considerada, caso contrário, o find
comando continua com o próximo caminho. Isso é usado no exemplo acima para encontrar arquivos que contêm a string hello
, mas para ignorar todos os outros arquivos.
O exemplo acima ilustra os dois usos mais comuns casos de -exec
:
- Como um teste para restringir ainda mais a pesquisa.
- Para realizar algum tipo de ação no encontrado nome do caminho (normalmente, mas não necessariamente, no final do comando
find
).
Usando -exec
em combinação com sh -c
O comando que -exec
pode executar é limitado a um utilitário externo com argumentos opcionais.Usar shell built-ins, funções, condicionais, pipelines, redirecionamentos etc. diretamente com -exec
não é possível, a menos que esteja envolvido em algo como sh -c
shell filho.
Se bash
recursos forem necessários, use bash -c
no lugar de sh -c
.
sh -c
executa /bin/sh
com um script fornecido na linha de comando, seguido por argumentos de linha de comando opcionais para esse script.
Um exemplo simples de uso de sh -c
sozinho, sem find
:
sh -c "echo "You gave me $1, thanks!"" sh "apples"
Isso passa dois argumentos para o script shell filho. Eles serão colocados em $0
e $1
para o script usar.
-
O string
sh
. Ele estará disponível como$0
dentro do script, e se o shell interno gerar uma mensagem de erro, ele irá prefixá-lo com esta string. -
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 para$0
que não faria parte de"$@"
).
Isso é útil em combinação com -exec
, pois nos permite fazer scripts arbitrariamente complexos que atuam nos nomes de caminho encontrados por find
.
Exemplo: Encontre todos os arquivos regulares que têm um determinado sufixo de nome de arquivo e mude esse sufixo de nome de arquivo para algum outro sufixo, onde os sufixos são mantidos em 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 inte script rnal, $1
seria a string text
, $2
seria a string txt
e $3
seria qualquer nome de caminho que find
encontrou para nós. O parâmetro expansion ${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 distintos das variáveis com os mesmos nomes no script externo.
O acima é a maneira correta de chamar um script complexo arbitrário de -exec
com find
. Usar find
em um loop como
for pathname in $( find ... ); do
é sujeito a erros e deselegante (opinião pessoal). Ele divide os nomes dos arquivos em espaços em branco, invoca o globbing do nome do arquivo e também força o shell a expandir o resultado completo de find
antes mesmo de executar a primeira iteração do loop.
Veja também:
- Por que o loop sobre a saída do find ' s é uma prática ruim?
- É possível usar `find -exec sh -c` com segurança?
Usando -exec ... {} +
O ;
no final pode ser substituído por +
. Isso faz com que find
execute o comando fornecido com tantos argumentos (nomes de caminhos encontrados) quanto possível, em vez de uma vez para cada nome de caminho encontrado. A string {}
deve ocorrer imediatamente antes de +
para que isso funcione .
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} +
Aqui, find
coletará os nomes de caminho resultantes e executará cat
no 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 como poucas vezes possível. Este último exemplo requer GNU mv
de coreutils (que suporta a opção -t
).
Usando -exec sh -c ... {} +
também é uma maneira eficiente de percorrer um conjunto de nomes de caminho com um script arbitrariamente complexo.
O básico é o mesmo de usar -exec sh -c ... {} ";"
, mas o script agora leva uma lista muito mais longa de argumentos. Eles podem ser repetidos repetindo "$@"
dentro do script.
Nosso exemplo da última seção que muda os sufixos de nome de 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
Há também -execdir
( implementado pela maioria das find
variantes, mas não é uma opção padrão).
Isso funciona como -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 caminho encontrado sem seu caminho (mas GNU find
ainda irá prefixar o nome de base com ./
, enquanto BSD find
não fará isso).
Exemplo:
find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \;
Isso moverá cada *.txt
-arquivo encontrado para um done-texts
subdiretório no mesmo diretório onde o arquivo estava encontrado . O arquivo também será renomeado adicionando o sufixo .done
a ele.
Isso seria um pouco mais complicado de fazer com -exec
já que teríamos que obter o nome de base do arquivo encontrado de {}
para formar o novo nome do arquivo. Também precisamos do nome do diretório de {}
para localizar o diretório done-texts
corretamente.
Com -execdir
, algumas coisas como essas se tornam mais fáceis.
A operação correspondente usando -exec
em 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 {} +
Comentários
-
-exec
pega um programa e argumentos e os executa; alguns comandos do shell consistem apenas em um programa e argumentos, mas muitos não. Um comando shell pode incluir redirecionamento e tubulação;-exec
não pode (embora todo ofind
possa ser redirecionado). Um comando shell pode usar; && if
etc;-exec
não pode, embora-a -o
possa fazer alguns. Um comando shell pode ser um alias ou uma função shell, ou embutido;-exec
não pode. Um comando shell pode expandir vars;-exec
não pode (embora o shell externo que executa ofind
possa). Um comando shell pode substituir$(command)
de maneira diferente a cada vez;-exec
não pode. … - Dizer que ' o comando shell sa está errado aqui,
find -exec cmd arg \;
não ' para chamar um shell para interpretar uma linha de comando shell, ele executaexeclp("cmd", "arg")
diretamente, nãoexeclp("sh", "-c", "cmd arg")
(para o qual o shell acabaria fazendo o equivalente aexeclp("cmd", "arg")
secmd
não estivesse embutido). - Você poderia esclarecer que tudo os
find
argumentos após-exec
e até;
ou+
crie o comando a ser executado junto com seus argumentos, com cada instância de um argumento{}
substituído pelo arquivo atual (com;
), e{}
como o último argumento antes de+
substituído por uma lista de arquivos como argumentos separados (no{} +
caso). IOW-exec
leva vários argumentos, terminados por um;
ou{}
+
. - @Kusalananda Wouldn ' e seu último exemplo também funciona com este comando mais simples:
find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'
? - @Atralb Sim, isso também funcionaria e teria o mesmo efeito do último trecho de código, mas em vez de executar
mv
em um loop, uma vez por arquivo encontrado, você executash
emv
para cada arquivo encontrado, que será visivelmente mais lento para grandes quantidades de arquivos.
man
antiga lê A utility_name ou argumento contendo apenas os dois caracteres " {} " deve ser substituído d pelo nome do caminho atual , que parece ser suficiente para mim. Adicionalmente, tem um exemplo com-exec rm {} \;
, assim como em sua pergunta. Na minha época, quase não havia outros recursos além da " grande parede cinza ", livros impressosman
páginas (o papel era mais barato do que armazenamento). Portanto, sei que isso é suficiente para alguém novo no assunto. Sua última pergunta é justo perguntar aqui. Infelizmente, nem @Kusalananda nem eu temos uma resposta para isso.