Procura recursivamente um padrão / texto apenas no nome do arquivo especificado de um diretório?


16

Eu tenho um diretório (por exemplo, abc/def/efg) com muitos subdiretórios (por exemplo ::) abc/def/efg/(1..300). Todos esses subdiretórios têm um arquivo comum (por exemplo, file.txt). Quero pesquisar uma string apenas neste, file.txtexcluindo outros arquivos. Como posso fazer isso?

Eu usei grep -arin "pattern" *, mas é muito lento se tivermos muitos subdiretórios e arquivos.


Respostas:


21

No diretório pai, você pode usar finde executar grepapenas esses arquivos:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +

2
Sugiro também que passe -Hpara grepque, nos casos em que apenas um caminho seja passado, esse caminho ainda seja impresso (em vez de apenas as linhas correspondentes do arquivo).
Eliah Kagan

24

Você também pode usar a globstar.

Construir grepcomandos com find, como na resposta de Zanna , é uma maneira altamente robusta, versátil e portátil de fazer isso (consulte também a resposta de sudodus ). E muru publicou uma excelente abordagem ao usar grepa --includeopção . Mas se você deseja usar apenas o grepcomando e seu shell, existe outra maneira de fazer isso - você pode fazer com que o próprio shell execute a recursão necessária :

shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt

A -Hbandeira faz grepmostrar o nome do arquivo, mesmo que apenas um arquivo correspondente seja encontrado. Você pode passar o -a, -ie -nbandeiras (a partir do seu exemplo) para grep, assim, se é isso que você precisa. Mas não passe -rou -Rao usar este método. É o shell que repete os diretórios na expansão do padrão global que contém **, e nãogrep .

Essas instruções são específicas para o shell Bash. O Bash é o shell de usuário padrão no Ubuntu (e na maioria dos outros sistemas operacionais GNU / Linux); portanto, se você está no Ubuntu e não sabe qual é o seu shell, é quase certamente o Bash. Embora os shells populares geralmente suportem **globs que atravessam o diretório , eles nem sempre funcionam da mesma maneira. Para mais informações, consulte Stéphane Chazelas 's excelente resposta a O resultado de ls *, ls ** e ls *** em Unix.SE .

Como funciona

Ligar o globstar festa opção shell faz **caminhos jogo contendo o separador de diretório ( /). É, portanto, um globo recorrente no diretório. Especificamente, como man bashexplica:

Quando a opção shell globstar está ativada e * é usado em um contexto de expansão de nome de caminho, dois * s adjacentes usados ​​como um padrão único correspondem a todos os arquivos e zero ou mais diretórios e subdiretórios. Se seguido por /, dois * s adjacentes corresponderão apenas a diretórios e subdiretórios.

Você deve ter cuidado com isso, pois é possível executar comandos que modificam ou excluem muito mais arquivos do que você pretende, especialmente se você escrever **quando pretender *. (É seguro neste comando, que não altera nenhum arquivo.) shopt -u globstarDesativa a opção shell do globstar.

Existem algumas diferenças práticas entre globstar e find.

findé muito mais versátil que a globstar. Tudo o que você pode fazer com a globstar, também pode fazer com o findcomando. Eu gosto da globstar, e às vezes é mais conveniente, mas a globstar não é uma alternativa geralfind .

O método acima não procura nos diretórios cujos nomes começam com a .. Às vezes você não deseja recursar essas pastas, mas às vezes deseja.

Como em um globo comum, o shell cria uma lista de todos os caminhos correspondentes e os passa como argumentos para o seu comando ( grep) no lugar do próprio globo. Se você tiver tantos arquivos chamados file.txtque o comando resultante seria muito longo para a execução do sistema, o método acima falhará. Na prática, você precisaria (pelo menos) de milhares desses arquivos, mas isso poderia acontecer.

Os métodos utilizados findnão estão sujeitos a esta restrição, porque:

  • O jeito de Zanna cria e executa um grepcomando com potencialmente muitos argumentos de caminho. Porém, se forem encontrados mais arquivos do que os que podem ser listados em um único caminho, a ação +terminada -execexecutará o comando com alguns dos caminhos, em seguida, executará novamente com mais alguns caminhos e assim por diante. No caso de greping para uma cadeia de caracteres em vários arquivos, isso produz o comportamento correto.

    Como o método globstar abordado aqui, ele imprime todas as linhas correspondentes, com caminhos anexados a cada um.

  • O caminho de sudodus é executado grepseparadamente para cada um file.txtencontrado. Se houver muitos arquivos, pode ser mais lento que alguns outros métodos, mas funciona.

    Esse método localiza arquivos e imprime seus caminhos, seguidos por linhas correspondentes, se houver. Este é um formato de saída diferente do formato produzido pelo meu método, o de Zanna e o de Muru .

Obtendo cores com find

Um dos benefícios imediatos do uso da globstar é, por padrão no Ubuntu, grepproduzir uma saída colorida. Mas você pode facilmente obter isso com find, também .

As contas de usuário no Ubuntu são criadas com um alias que greprealmente funciona grep --color=auto(execute alias greppara ver). É bom que os aliases sejam expandidos apenas quando você os emitir de maneira interativa , mas isso significa que, se você quiser findchamar grepcom o --colorsinalizador, precisará escrevê-lo explicitamente. Por exemplo:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +

Você pode afirmar mais claramente que precisa usar o bashshell para que isso funcione. Você não dizê-lo implicitamente "a opção shell globstar bash" mas pode ser facilmente perdida por pessoas que lêem muito rapidamente.
perfil completo de Stig Hemmer

Eu removi minha resposta porque causou muitos comentários críticos. Portanto, você deve remover a referência a ele na sua resposta.
sudodus

@ StigHemmer Obrigado - Esclareci que nem todas as conchas têm esse recurso. Embora muitas conchas (não apenas o bash) suportem **globs que atravessam o diretório , sua crítica principal está correta: a apresentação **desta resposta é específica do bash, com shopt sendo apenas bash e o termo "globstar" sendo (eu acho) bash e Apenas tcsh. Eu havia ignorado isso originalmente por causa dessas complexidades, mas você está certo que é um pouco confuso. Em vez de discuti-lo detalhadamente nesta resposta, vinculei-me a outro post (bastante completo) que faz o trabalho pesado.
Eliah Kagan

@sudodus Eu fiz isso, mas espero que seja temporário. Eu e outros consideramos sua resposta valiosa. É verdade -eque não deve ser aplicado aos caminhos, mas isso é facilmente corrigido. Para o primeiro comando, apenas omita -e. Para o segundo, use find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;ou find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Às vezes, os usuários preferem o seu caminho (com -euso fixo) aos outros, que imprimem um caminho por linha correspondente ; o seu imprime um caminho por arquivo encontrado, seguido pelos grepresultados.
Eliah Kagan

@sudodus Então, por grepsi só, não fará o que você está fazendo. Algumas outras críticas também estavam erradas. grep -Hexecutado por -execnão será colorido sem --color(ou GREP_COLOR). IEEE 1.003,1-2.008 não garante {}expande em ##### {}:, mas Ubuntu tem GNU encontrar, o que faz . Se estiver tudo bem com você , editarei sua postagem para corrigir o -eerro (e esclarecer seu caso de uso) e você poderá ver se deseja cancelar a exclusão. (Eu tenho o representante para ver / editar mensagens eliminadas.)
Elias Kagan

18

Você não precisa finddisso; greppode lidar com isso perfeitamente bem por conta própria:

grep "pattern" . -airn --include="file.txt"

De man grep:

--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).

Bom - esse parece ser o melhor caminho. Simples e eficiente. Eu gostaria de ter conhecido (ou pensado em verificar a página de manual) sobre esse método. Obrigado!
Eliah Kagan

@EliahKagan Estou mais surpreso que Zanna não tenha postado isso - eu havia mostrado um exemplo dessa opção para outra resposta há algum tempo. :)
muru

2
lento aluno, infelizmente, mas eu chegar lá um dia, seus ensinamentos não são completamente desperdiçados em mim;)
Zanna

Isso é muito simples e fácil de lembrar. Obrigado.
Rajesh Keladimath

Eu concordo que esta é a melhor resposta. Devo remover minha resposta para diminuir a confusão, ou deixá-la ficar para mostrar que existem alternativas e o que pode ser feito com?find?
sudodus

8

O método dado na resposta do muru , de executar grepcom o --includesinalizador para especificar um nome de arquivo, geralmente é a melhor opção. No entanto, isso também pode ser feito com find.

A abordagem nesta resposta é findexecutada grepseparadamente para cada arquivo encontrado e imprime o caminho de cada arquivo exatamente uma vez , acima das linhas correspondentes encontradas em cada arquivo. (Os métodos que imprimem o caminho na frente de cada linha correspondente são abordados em outras respostas.)


Você pode alterar o diretório para o topo da árvore de diretórios em que você possui esses arquivos. Então corra:

find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;

Isso imprime o caminho (relativo ao diretório atual ., e incluindo o próprio nome do arquivo) de cada arquivo nomeado file.txt, seguido por todas as linhas correspondentes no arquivo. Isso funciona porque {}é um espaço reservado para o arquivo encontrado. O caminho de cada arquivo é separado de seu conteúdo, sendo prefixado com #####e é impresso apenas uma vez, antes das linhas correspondentes desse arquivo. (Os arquivos chamados file.txtque não contêm correspondências ainda têm seus caminhos impressos.) Você pode achar essa saída menos confusa do que a obtida com métodos que imprimem um caminho no início de cada linha correspondente.

Usar finddessa maneira quase sempre será mais rápido do que executar grepem todos os arquivos ( grep -arin "pattern" *), porque findprocura os arquivos com o nome correto e ignora todos os outros arquivos.

O Ubuntu usa o GNU find , que sempre se expande {}mesmo quando aparece em uma string maior , como ##### {}:. Se você precisar que seu comando trabalhe findem sistemas que talvez não suportem isso , ou se prefere usar a -execação somente quando for absolutamente necessário, poderá usar:

find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;

Para facilitar a leitura da saída , você pode usar seqüências de escape ANSI para obter nomes de arquivos coloridos. Isso faz com que o cabeçalho de cada arquivo se destaque melhor das linhas correspondentes impressas abaixo dele:

find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;

Isso faz com que o seu shell transforme o código de escape para verde na sequência de escape real que produz verde em um terminal e faça o mesmo com o código de escape para cores normais. Essas fugas são passadas para find, que as usam quando imprime um nome de arquivo. (a $' 'cotação é necessária aqui porque finda -printfação de não reconhece \epara interpretar os códigos de escape ANSI.)

Se preferir, você pode usar -execcom o printfcomando do sistema (que suporta \e). Então, outra maneira de fazer a mesma coisa é:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;

eu estava indo para fazer um "for loop" com uma matriz e eu não pensei sobre a opção nativa exec do find. Um bom! Mas acho que usar dot o localizará no diretório em que você já está. Corrija-me se eu estiver errada. Não seria melhor especificar o diretamente para analisar na ordem de localização? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv

Claro, que irá eliminar o cd abc/def/efgcomando 'mudança diretório' :-)
sudodus

(1) Por que você está especificando a -eopção echo? Isso fará com que ele altere os nomes de arquivos que contêm barras invertidas. (2) Não é garantido que o uso {}como parte de um argumento funcione. Seria melhor dizer -exec echo "#####" {} \;ou -exec printf "##### %s:\n" {} \;. (3) Por que não usar -printou apenas -printf? (4) Considere também grep -H.
G-Man diz 'Reinstate Monica'

@ G-man, 1) Porque eu usei a cor ANSI originalmente: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Você pode estar certo, mas até agora isso está funcionando para mim. 3) -print e -printf também são alternativas. 4) Isso já está lá na resposta principal. - De qualquer forma, você é bem-vindo com a sua própria resposta :-)
sudodus

Você não precisa das duas -execligações. Basta usar grep -He isso imprimirá o nome do arquivo (em cores) e o texto correspondente.
terdon

0

Apenas para apontar que, se as condições da pergunta puderem ser consideradas literárias, você poderá usar o grep direto:

grep 'pattern' abc/def/efg/*/file.txt

ou

grep 'pattern' abc/def/efg/{1..300}/file.txt
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.