Como usar a opção '-prune' de 'find' no sh?

219

Eu não entendo bem o exemplo dado pelo man find, alguém pode me dar alguns exemplos e explicações? Posso combinar expressão regular?


A pergunta mais detalhada é assim:

Escreva um script de shell changeall, que tenha uma interface semelhante changeall [-r|-R] "string1" "string2". Ele vai encontrar todos os arquivos com um sufixo .h, .C, .cc, ou .cppe alterar todas as ocorrências string1para string2. -ré uma opção para permanecer apenas no diretório atual ou incluir subdir.

NOTA:

  1. Para casos não recursivos, lsNÃO é permitido, só poderíamos usar finde sed.
  2. Eu tentei, find -depthmas não era suportado. É por isso que eu queria saber se -prunepoderia ajudar, mas não entendi o exemplo man find.

EDIT2: Eu estava realizando uma tarefa, não fiz perguntas em grandes detalhes porque gostaria de terminar pessoalmente. Desde que eu já fiz e entreguei, agora posso declarar a questão toda. Além disso, consegui terminar a tarefa sem usá-la -prune, mas gostaria de aprender de qualquer maneira.

derrdji
fonte

Respostas:

438

O que eu achei confuso -pruneé que é uma ação (como -print), não um teste (como -name). Ele altera a lista "tarefas", mas sempre retorna verdadeiro .

O padrão geral de uso -pruneé o seguinte:

find [path] [conditions to prune] -prune -o \
            [your usual conditions] [actions to perform]

Você quase sempre deseja o -o(OR lógico) imediatamente após -prune, porque a primeira parte do teste (até e inclusive -prune) retornará falso para o que você realmente deseja (ou seja: o que você não quer remover).

Aqui está um exemplo:

find . -name .snapshot -prune -o -name '*.foo' -print

Isso encontrará os arquivos "* .foo" que não estão nos diretórios ".snapshot". Neste exemplo, -name .snapshotcompõe o [conditions to prune], e -name '*.foo' -printé [your usual conditions]e [actions to perform].

Notas importantes :

  1. Se tudo o que você deseja fazer é imprimir os resultados, você pode estar acostumado a deixar de fora a -printação. Você geralmente não quer fazer isso ao usar -prune.

    O comportamento padrão de localização é "e" a expressão inteira com a -printação se não houver outras ações além de -prune(ironicamente) no final. Isso significa que escrever isso:

    find . -name .snapshot -prune -o -name '*.foo'              # DON'T DO THIS

    é equivalente a escrever isso:

    find . \( -name .snapshot -prune -o -name '*.foo' \) -print # DON'T DO THIS

    o que significa que também imprimirá o nome do diretório que você está removendo, o que geralmente não é o que você deseja. Em vez disso, é melhor especificar explicitamente a -printação, se é isso que você deseja:

    find . -name .snapshot -prune -o -name '*.foo' -print       # DO THIS
  2. Se a sua "condição usual" corresponder a arquivos que também correspondem à sua condição de remoção, esses arquivos não serão incluídos na saída. A maneira de corrigir isso é adicionar um -type dpredicado à sua condição de remoção.

    Por exemplo, suponha que desejássemos remover qualquer diretório que começou com .git(isso é admitido de certa forma artificial - normalmente você só precisa remover a coisa com o nome exato .git ), mas que não fosse isso, queria ver todos os arquivos, incluindo arquivos como .gitignore. Você pode tentar o seguinte:

    find . -name '.git*' -prune -o -type f -print               # DON'T DO THIS

    Isso não incluiria .gitignorena saída. Aqui está a versão fixa:

    find . -name '.git*' -type d -prune -o -type f -print       # DO THIS

Dica extra: se você estiver usando a versão GNU do find, a página texinfo para findtem uma explicação mais detalhada do que sua página de manual (como é verdade para a maioria dos utilitários GNU).

Laurence Gonsalves
fonte
6
não é 100% óbvio no seu texto (mas como você imprime apenas '* .foo', não entra em conflito), mas a parte -une também não imprime nada (não apenas diretórios) chamado ".snapshot". ou seja, -prunenão funciona apenas em diretórios (mas, para diretórios, também impede a inserção de diretórios correspondentes a essa condição, ou seja, aqui os diretórios correspondentes a essa -name .snapshot).
Olivier Dulac
12
e +1 para você pela explicação bem-feita (e especialmente pela observação importante). Você deve enviar isso para os desenvolvedores de busca (como a página de manual não explica "poda" para seres humanos normais ^^ Foram necessárias muitas tentativas para descobrir isso, e eu não vi o efeito colateral que você nos adverte)
Olivier Dulac
2
@OlivierDulac Esse é um ponto muito bom sobre potencialmente remover os arquivos que você deseja manter. Atualizei a resposta para esclarecer isso. não é realmente -pruneele mesmo que causa isso, a propósito. A questão é que o operador ou "curto-circuito" e / ou tem precedência menor que e. O resultado final é que, se um arquivo chamado .snapshotfor encontrado, ele corresponderá ao primeiro -name, -prunenão fará nada (mas retornará verdadeiro) e, em seguida, o ou retornará verdadeiro, pois o argumento esquerdo era verdadeiro. A ação (por exemplo -print:) faz parte de seu segundo argumento, portanto nunca tem uma chance de ser executada.
Laurence Gonsalves
3
+1 finalmente descobri por que eu preciso -printno final, agora eu posso parar de adicionar \! -path <pattern>além-prune
Variable Miserable
6
Observe que "-o" é uma abreviação de "-ou", que (embora não seja compatível com POSIX) lê mais claramente.
yoyo
27

Normalmente, a maneira nativa em que fazemos as coisas no linux e a maneira como pensamos é da esquerda para a direita.
Então, você escreveria o que procurava primeiro:

find / -name "*.php"

Então você provavelmente clica em enter e percebe que está obtendo muitos arquivos de diretórios que não deseja. Vamos excluir / media para evitar procurar nas unidades montadas.
Agora você deve apenas ANEXAR o seguinte ao comando anterior:

-print -o -path '/media' -prune

então o comando final é:

find / -name "*.php" -print -o -path '/media' -prune

............... | <--- Incluir ---> | .................... | <- -------- Excluir ---------> |

Eu acho que essa estrutura é muito mais fácil e se correlaciona com a abordagem correta

AmitP
fonte
3
Eu não esperava que isso fosse eficiente - eu pensaria que ele avaliaria a cláusula esquerda antes da ameixa, mas, para minha surpresa, um teste rápido parece sugerir que findé inteligente o suficiente para processar a -prunecláusula primeiro. Hummm, interessante.
Artfulrobot
Eu nunca tinha considerado que em quase uma década usando o GNU find! Obrigado por isso! Definitivamente mudará a maneira de pensar a -prunepartir de agora.
Felipe Alvarez
3
@artfulrobot Realmente está processando primeiro? Eu teria pensado que ele estava entrando /media, notando que não é chamado *.phpe depois verificando se está atualmente dentro /media, vendo que está e, portanto, pulando toda a subárvore inteira. Ainda é da esquerda para a direita, não faz diferença desde que as duas verificações não se sobreponham.
Phd #
26

Cuidado que -prune não impede a descida em nenhum diretório, como alguns já disseram. Impede a descida em diretórios que correspondem ao teste ao qual é aplicado. Talvez alguns exemplos ajudem (veja a parte inferior para um exemplo de regex). Desculpe por isso ser tão demorado.

$ find . -printf "%y %p\n"    # print the file type the first time FYI
d .
f ./test
d ./dir1
d ./dir1/test
f ./dir1/test/file
f ./dir1/test/test
d ./dir1/scripts
f ./dir1/scripts/myscript.pl
f ./dir1/scripts/myscript.sh
f ./dir1/scripts/myscript.py
d ./dir2
d ./dir2/test
f ./dir2/test/file
f ./dir2/test/myscript.pl
f ./dir2/test/myscript.sh

$ find . -name test
./test
./dir1/test
./dir1/test/test
./dir2/test

$ find . -prune
.

$ find . -name test -prune
./test
./dir1/test
./dir2/test

$ find . -name test -prune -o -print
.
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2

$ find . -regex ".*/my.*p.$"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test/myscript.pl

$ find . -name test -prune -regex ".*/my.*p.$"
(no results)

$ find . -name test -prune -o -regex ".*/my.*p.$"
./test
./dir1/test
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py
./dir2/test

$ find . -regex ".*/my.*p.$" -a -not -regex ".*test.*"
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.py

$ find . -not -regex ".*test.*"                   .
./dir1
./dir1/scripts
./dir1/scripts/myscript.pl
./dir1/scripts/myscript.sh
./dir1/scripts/myscript.py
./dir2
Pausado até novo aviso.
fonte
se você também "tocar em ./dir1/scripts/test" (ou seja, tiver um arquivo "test" e não dir, no subdir impresso), ele não será impresso pelo find . -name test -prune -o -print: iow, -pruneé uma ação que também trabalha em arquivos
Olivier Dulac
10

Adicionando ao conselho dado em outras respostas (não tenho representante para criar respostas) ...

Ao combinar -prunecom outras expressões, há uma diferença sutil no comportamento, dependendo de quais outras expressões são usadas.

O exemplo do @Laurence Gonsalves encontrará os arquivos "* .foo" que não estão nos diretórios ".snapshot": -

find . -name .snapshot -prune -o -name '*.foo' -print

No entanto, essa mão curta ligeiramente diferente irá, talvez inadvertidamente, também listar o .snapshotdiretório (e qualquer diretório .snapshot aninhado): -

find . -name .snapshot -prune -o -name '*.foo'

O motivo é (de acordo com a página de manual no meu sistema): -

Se a expressão especificada não contiver nenhuma das primárias -exec, -ls, -ok ou -print, a expressão especificada será efetivamente substituída por:

(dada_expressão) -impressão

Ou seja, o segundo exemplo é o equivalente a inserir o seguinte, modificando assim o agrupamento de termos: -

find . \( -name .snapshot -prune -o -name '*.foo' \) -print

Isso foi observado pelo menos no Solaris 5.10. Tendo usado vários sabores do * nix por aproximadamente 10 anos, só recentemente procurei uma razão pela qual isso ocorre.

crw
fonte
Obrigado por chamar a atenção para a diferença entre usar -prunecom e sem -print!
Mcw 01/12/19
3

A remoção é uma não recursão em nenhuma alternância de diretório.

Na página do manual

Se -phth não for fornecido, true; se o arquivo for um diretório, não desça nele. Se -thth for fornecida, false; sem efeito.

Basicamente, ele não será incluído em nenhum subdiretório.

Veja este exemplo:

Você tem os seguintes diretórios

  • / home / test2
  • / home / test2 / test2

Se você executar find -name test2:

Ele retornará os dois diretórios

Se você executar find -name test2 -prune:

Ele retornará apenas / home / test2, pois não descerá em / home / test2 para encontrar / home / test2 / test2

AdamW
fonte
não é 100% verdadeiro: é "faça a poda ao combinar a condição e, se for um diretório, retire-o da lista de tarefas, ou seja, não o insira também". -une também funciona em arquivos.
Olivier Dulac
2

Não sou especialista nisso (e esta página foi muito útil junto com http://mywiki.wooledge.org/UsingFind )

O que acabamos de notar -pathé para um caminho que corresponde totalmente à string / caminho que vem logo depoisfind ( .nesses exemplos), onde as -namecorresponde a todos os nomes de base.

find . -path ./.git  -prune -o -name file  -print

bloqueia o diretório .git no seu diretório atual ( como encontrado em . )

find . -name .git  -prune -o -name file  -print

bloqueia todos os subdiretórios .git recursivamente.

Observe o ./ é extremamente importante !! -pathdeve corresponder a um caminho ancorado . ou o que vier logo após a localização, se você conseguir correspondências sem ele (do outro lado do ou ' -o'), provavelmente não sendo podado! Eu era ingenuamente inconsciente disso e me pôs a usar -path quando é ótimo quando você não deseja remover todos os subdiretórios com o mesmo nome de base: D

sabgenton
fonte
Nota Se a sua opinião fazendo find bla/então você precisaria -caminho bla/.git (ou se você empurrou um *na frente em vez disso, iria se comportar mais como -nome)
sabgenton
1

Mostre tudo, incluindo o próprio diretório, mas não o conteúdo longo e chato:

find . -print -name dir -prune
Devon
fonte
0

Se você ler todas as boas respostas aqui, meu entendimento agora é que todos os seguintes retornam os mesmos resultados:

find . -path ./dir1\*  -prune -o -print

find . -path ./dir1  -prune -o -print

find . -path ./dir1\*  -o -print
#look no prune at all!

Mas o último levará muito mais tempo, pois ainda pesquisa tudo no diretório1. Acho que a verdadeira questão é como obter -orresultados indesejados sem realmente procurá-los.

Acho que podar significa não fazer partidas passadas decentes, mas marcar como concluídas ...

http://www.gnu.org/software/findutils/manual/html_mono/find.html "No entanto, isso não se deve ao efeito da ação '-prune' (que impede apenas descidas adicionais, não garante ignoramos esse item.) Em vez disso, esse efeito se deve ao uso de '-o'. Como o lado esquerdo da condição “ou” foi bem-sucedido em ./src/emacs, não é necessário avaliar a lado da mão ('-print') para este arquivo em particular. "

sabgenton
fonte
0

findcria uma lista de arquivos. Aplica o predicado que você forneceu a cada um e retorna aqueles que passam.

Essa ideia que -prunesignifica excluir dos resultados foi realmente confusa para mim. Você pode excluir um arquivo sem remoção:

find -name 'bad_guy' -o -name 'good_guy' -print  // good_guy

Tudo o que -prunefaz é alterar o comportamento da pesquisa. Se a correspondência atual for um diretório, ele diz "ei find, aquele arquivo que você acabou de corresponder, não desce nele" . Apenas remove essa árvore (mas não o próprio arquivo) da lista de arquivos a serem pesquisados.

Deve ser nomeado -dont-descend.

seeker_of_bacon
fonte
0

Existem algumas respostas; alguns deles são muito teóricos. Vou deixar por que precisei remover uma vez, então talvez o tipo de explicação sobre necessidade / primeiro exemplo seja útil para alguém :)

Problema

Eu tinha uma pasta com cerca de 20 diretórios de nós, cada um com seu node_modulesdiretório conforme o esperado.

Depois de entrar em qualquer projeto, você vê cada um ../node_modules/module. Mas você sabe como é. Quase todos os módulos têm dependências, então o que você está vendo é mais parecido comprojectN/node_modules/moduleX/node_modules/moduleZ...

Eu não queria me afogar com uma lista com a dependência da dependência de ...

Conhecer -d n/ -depth n, isso não teria me ajudado, pois o diretório principal / primeiro node_modules que eu queria de cada projeto estava em uma profundidade diferente, assim:

Projects/MysuperProjectName/project/node_modules/...
Projects/Whatshisname/version3/project/node_modules/...
Projects/project/node_modules/...
Projects/MysuperProjectName/testProject/november2015Copy/project/node_modules/...
[...]

Como posso obter a primeira lista de caminhos que terminam na primeira node_modulese passar para o próximo projeto para obter a mesma?

Entrar -prune

Ao adicionar -prune, você ainda terá uma pesquisa recursiva padrão. Cada "caminho" é analisado, e todo achado é cuspido e findcontinua cavando como um bom sujeito. Mas é a busca por mais do node_modulesque eu não queria.

Portanto, a diferença é que, em qualquer um desses caminhos diferentes, -pruneserá findnecessário parar de ir mais longe nessa avenida em particular quando encontrar o item. No meu caso, a node_modulespasta.

Carles Alcolea
fonte