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

  • @Philippos: Entendo. Lembre-se de que as páginas de manual são uma referência, ou seja, úteis para aqueles com uma compreensão do assunto para pesquisar a sintaxe. Para alguém novo no tópico, eles são frequentemente muito enigmáticos e formais para serem úteis. Você verá que a resposta aceita é cerca de 10 vezes mais longa que a entrada da página de manual, e que ' s por uma razão.
  • Até mesmo a página POSIX 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 impressos man 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.
  • @Philippos 🙂 Oh, isso seria uma coisa emocionante de se tentar, para um nível de " emocionante " que está fora da escala do meu gráfico.
  • Comeon @Philippos. Você está realmente dizendo a Kusalananda que ele não melhorou a página de manual? 🙂
  • @ZsoltSzilagy Eu não disse isso nem quis dizer isso. Ele o alimentou muito bem, só acho que você já tem idade para comer sozinho. (-;

Resposta

Esta resposta vem nas seguintes partes:

  • Uso básico de -exec
  • Usando -exec em combinação com sh -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:

  1. Como um teste para restringir ainda mais a pesquisa.
  2. 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.

  1. 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.

  2. 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:


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 o find 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 o find 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 executa execlp("cmd", "arg") diretamente, não execlp("sh", "-c", "cmd arg") (para o qual o shell acabaria fazendo o equivalente a execlp("cmd", "arg") se cmd 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ê executa sh e mv para cada arquivo encontrado, que será visivelmente mais lento para grandes quantidades de arquivos.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *