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

15

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 ). Eu quero pesquisar uma string somente neste file.txt excluindo outros arquivos. Como posso fazer isso?

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

    
por Rajesh Keladimath 03.01.2017 / 13:49

5 respostas

21

No diretório pai, você pode usar find e executar grep apenas nesses arquivos:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
    
por Zanna 03.01.2017 / 13:56
23

Você também pode usar globstar.

Construir comandos grep com find , como na resposta da Zanna , é altamente robusto, versátil e forma portátil de fazer isso (veja também resposta do sudodus ). E o muru postou uma excelente abordagem de usar grep ' --include option . Mas se você quiser usar apenas o comando grep e seu shell, existe outra maneira de fazê-lo - você pode fazer o o próprio shell executar a recursão necessária :

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

O sinal -H faz com que grep mostre o nome do arquivo, mesmo se apenas um arquivo correspondente for encontrado. Você pode passar os sinalizadores -a , -i e -n (de seu exemplo) para grep também, se necessário. Mas não passe -r ou -R ao usar este método. É o shell que recursa diretórios ao expandir o padrão glob contendo ** e não grep .

Estas instruções são específicas para o shell Bash. Bash é o shell do usuário padrão no Ubuntu (e a maioria dos outros sistemas operacionais GNU / Linux), então se você estiver no Ubuntu e don não sei o que é a sua concha, é quase certamente Bash. Embora os shells populares geralmente suportem ** globs, eles nem sempre funcionam da mesma maneira. Para mais informações, consulte Stéphane Chazelas 's excelente resposta para O resultado de ls *, ls ** e ls *** em Unix.SE .

Como funciona

Ativando a globstar bash faz com que ** corresponda a caminhos contendo o separador de diretório ( / ). É, portanto, um glob de recursão de diretório. Especificamente, como man bash explica:

  

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

Você deve ter cuidado com isso, já que você pode executar comandos que modificam ou excluem muito mais arquivos do que você pretende, especialmente se você escreve ** quando você quis escrever * . (É seguro neste comando, que não altera nenhum arquivo).% Co_de% desativa a opção do shell globstar.

Existem algumas diferenças práticas entre globstar e shopt -u globstar .

find é muito mais versátil que globstar. Qualquer coisa que você possa fazer com globstar, você pode fazer com o comando find também. Eu gosto de globstar, e às vezes é mais conveniente, mas globstar não é uma alternativa geral para find .

O método acima não analisa os diretórios cujos nomes começam com find . Às vezes você não quer reciclar essas pastas, mas às vezes você faz isso.

Assim como em um glob comum, o shell cria uma lista de todos os caminhos correspondentes e os transmite como argumentos para o seu comando ( . ) no lugar do próprio glob. Se você tem tantos arquivos chamados grep que o comando resultante seria muito longo para o sistema executar, então o método acima falhará. Na prática, você precisaria (pelo menos) milhares desses arquivos, mas isso poderia acontecer.

Os métodos que usam file.txt não estão sujeitos a essa restrição porque:

  • A maneira de Zanna constrói e executa um comando find com potencialmente muitos argumentos de caminho. Mas, se forem encontrados mais arquivos do que os que podem ser listados em um único caminho, a ação grep -terminated + executará o comando com alguns dos caminhos, depois executará novamente com mais alguns caminhos e assim por diante. No caso de -exec ing para uma string em vários arquivos, isso produz o comportamento correto.

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

  • o caminho do sudodus executa grep separadamente para cada grep encontrado. Se houver muitos arquivos, pode ser mais lento que alguns outros métodos, mas funciona.

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

Obtendo cores com file.txt

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

As contas de usuário no Ubuntu são criadas com um alias que faz com que find realmente execute grep (execute grep --color=auto para ver). É uma coisa boa que os aliases são praticamente só expandiu quando você os emitiu de forma interativa , mas isso significa que se você quiser que alias grep invoque find com o grep , você terá que escrevê-lo explicitamente. Por exemplo:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
    
por Eliah Kagan 03.01.2017 / 18:02
19

Você não precisa de find para isso; grep pode lidar com isso perfeitamente bem sozinho:

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

Em 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).
    
por muru 03.01.2017 / 18:23
8

O método fornecido na resposta do muru , da execução de grep com o sinalizador --include para especificar um nome de arquivo, geralmente é a melhor escolha. No entanto, isso também pode ser feito com find .

A abordagem nesta resposta usa find para executar grep separadamente para cada arquivo encontrado e imprime o caminho para cada arquivo exatamente uma vez , acima das linhas correspondentes encontradas em cada Arquivo. (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 onde você tem 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 denominado file.txt , seguido por todas as linhas correspondentes no arquivo. Isso funciona porque {} é um marcador 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.txt que não contêm correspondências ainda têm seus caminhos impressos.) Você pode achar essa saída menos confusa do que a que obtém de métodos que imprimem um caminho no início de cada linha correspondente.

Usar find como este quase sempre será mais rápido do que executar grep em todos os arquivos ( grep -arin "pattern" * ), porque find procura os arquivos com o nome correto e ignora todos outros arquivos.

O Ubuntu usa o GNU find , que sempre expande {} mesmo quando aparece em uma string maior , como ##### {}: . Se você precisar de seu comando para trabalhar com find em sistemas que talvez não suportem isso , ou você prefere usar a ação -exec somente quando absolutamente necessário, você pode usar:

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

Para tornar a saída mais fácil de ler , você pode usar sequências de escape ANSI para obter nomes de arquivos coloridos. Isso faz com que o caminho do caminho de cada arquivo se destaque das linhas correspondentes impressas:

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

O faz com que o seu shell ative o código de escape para verde na seqüência de escape real que produz verde em um terminal, e para fazer a mesma coisa com o código de escape normal cor. Esses escapes são passados para find , que os usa quando imprime um nome de arquivo. ( $' ' cotação é necessária aqui porque a ação find do -printf não reconhece \e para interpretar códigos de escape ANSI.)

Se preferir, você pode usar -exec com printf do sistema comando (que suporta \e ). Então, outra maneira de fazer o mesmo é:

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

Apenas para indicar que, se as condições da questão puderem ser consideradas literárias, você pode usar o grep direto:

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

ou

grep 'pattern' abc/def/efg/{1..300}/file.txt
    
por JJoao 07.11.2017 / 09:18