procura recursiva por um padrão, em seguida, para cada correspondência, imprima a SEQUÊNCIA específica: número da linha, nome do arquivo e nenhum conteúdo do arquivo

5

O que eu estou procurando é quase exatamente o mesmo que pode ser encontrado aqui, mas eu quero o formato "número da linha, separador, nome do arquivo, nova linha" nos resultados, exibindo assim o número da linha no início da linha, não depois do nome do arquivo e sem exibindo a linha contendo a correspondência.

A razão pela qual este formato é preferível é que

  • (a) o nome do arquivo pode ser longo e críptico e conter o separador que a ferramenta usa para separar o nome do arquivo do número da linha, tornando incrivelmente difícil usar o awk para conseguir isso, já que o padrão dentro do arquivo também pode conter o mesmo separador. Além disso, os números de linha no início da linha serão alinhados melhor do que se aparecerem após o nome do arquivo. E a outra razão para esse formato desejado é que
  • (b) as linhas que combinam com o padrão podem ser muito longas e atrapalham a propriedade de uma linha por linha na saída exibida na saída padrão (e visualizar a saída na saída padrão é melhor do que ter para salvar em um arquivo e usar uma ferramenta como vi para ver uma linha por linha no arquivo de saída).

Agora que defini o requisito, considere isto:

  1. O Ack não está instalado no host Linux que estou usando, portanto não posso usá-lo.

  2. Se eu fizer o seguinte, o shell executará find . e substituirá 'find'. com uma lista de caminhos absolutos começando no diretório de trabalho atual e procedendo para baixo recursivamente:

    grep -n PATTERN 'find .'
    

    então o -n imprime o número da linha, mas não onde eu quero. Além disso, por algum motivo eu não entendo, se um nome de diretório inclui o PADRÃO, então o grep o iguala aos arquivos regulares que contêm o padrão. Isso não é o que eu quero então eu uso:

    grep -n PATTERN 'find . -type f'
    

    Eu também queria mudar este comando para que a saída do find fosse passada para grep dinamicamente. Em vez de ter que construir toda a lista de caminhos absolutos primeiro e depois passar a maior parte deles para o grep, achei passar cada linha para o grep como ele constrói a lista, então eu tentei:

    find . -exec grep -n PATTERN  '{}' \;
    

    que parece a sintaxe correta de acordo com o man page , mas quando eu emito este comando o shell Bash executa cerca de 100 vezes mais lento, então isso não é o caminho a percorrer.

Em vista do que descrevi, como posso executar algo semelhante a este comando e obtenha o formato desejado. Eu já listei os problemas associados com o post relacionado.

    
por John Sonderson 17.10.2013 / 01:29

2 respostas

4

Usando o grep

Por que você não pode simplesmente usar a opção -r para grep para recrutar o sistema de arquivos em vez de usar find ? Existem 2 switches adicionais que eu usaria também, em vez da opção -n .

$ grep -rHn PATTERN <DIR> | cut -d":" -f1-2

Exemplo # 1

$ grep -rHn PATH ~/.bashrc | cut -d":" -f1-2
/home/saml/.bashrc:25

Detalhes

  • -r - busca recursivamente por arquivos + diretórios
  • -H - imprime o nome do arquivo se ele corresponder (menos restritivo que -l ), ou seja, funciona com outros switches de grep
  • -n - exibe o número da linha da partida

Exemplo # 2

$ grep -rHn PATH ~/.bash* | cut -d":" -f1-2
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25

Usando o find

$ find . -exec sh -c 'grep -Hn PATTERN "$@" | cut -d":" -f1-2' {}  +

Exemplo

$ find ~/.bash* -exec sh -c 'grep -Hn PATH "$@" | cut -d":" -f1-2' {}  +
/home/saml/.bash_profile:10
/home/saml/.bash_profile:12
/home/saml/.bash_profile_askapache:99
/home/saml/.bash_profile_askapache:101
/home/saml/.bash_profile_askapache:118
/home/saml/.bash_profile_askapache:166
/home/saml/.bash_profile_askapache:218
/home/saml/.bash_profile_askapache:250
/home/saml/.bash_profile_askapache:314
/home/saml/.bash_profile_askapache:2317
/home/saml/.bash_profile_askapache:2323
/home/saml/.bashrc:25

Se você realmente quiser usar find , você pode fazer algo assim para exec grep ao localizar os arquivos usando find .

    
por 17.10.2013 / 01:51
4
grep -n PATTERN 'find . -type f'

Isso é ruim porque a saída de uma substituição de comando é interpretada como uma lista separada por espaço em branco de padrões de caractere curinga de nome de arquivo. Se algum dos nomes de arquivo contiver espaços em branco ou um de \[*? , esse fragmento não funcionará. Além disso, se houver muitos arquivos correspondentes, isso eventualmente resultará em uma linha de comando que é muito longa.

find . -exec grep -n PATTERN  '{}' \;

Isso é bom e confiável, mas grep é invocado uma vez por arquivo. É por isso que é tão lento.

Use -exec … {} + para executar o comando em lotes de tantos arquivos quanto possível. Note que pode acontecer que o último lote (ou em teoria outros) consiste em um único arquivo, então grep não imprime o nome do arquivo; passe a opção -H para sempre imprimir o nome do arquivo ou adicione o argumento /dev/null (que nunca contém correspondências, mas garante que grep veja pelo menos dois nomes de arquivos).

find . -type f -exec grep -Hn PATTERN {} +

O GNU grep não tem uma opção para imprimir números de linha correspondentes, mas não o texto da linha correspondente. Você pode retirar o texto correspondente e trocar os números de linha pelo nome do arquivo, com sed.

find . -type f -exec grep -Hn PATTERN {} + | sed 's/^\([^:]*\):\([^:]*\):.*/:/'

Se você quiser alinhar à direita os números de linha, o awk é muito mais simples do que qualquer alternativa que eu possa imaginar.

find . -type f -exec grep -Hn PATTERN {} + | awk -F : '{printf "%8d:%s", $2, $1}'

Você pode ganhar mais controle fazendo a correspondência no awk ao invés do grep. Awk tende a ser um pouco mais lento porque é uma ferramenta mais genérica com uma linguagem interpretada. Um benefício é que você pode escolher o que fazer com nomes de arquivos contendo um caractere de dois pontos ou nova linha, o que leva a uma saída ambígua do grep. O snippet a seguir usa o awk para fazer a pesquisa e lidar com nomes de arquivos que contêm : (e até mesmo novas linhas, mas, para esses, ele produz resultados ambíguos). Observe que o awk usa expressões regulares estendidas , como grep -E (com pequenas variações, mas não muito mais do que você recebe entre implementações do grep ou do awk).

find . -type f -exec awk '/PATTERN/ {printf "%d:", FNR; print FILENAME}' {} +
    
por 17.10.2013 / 01:59

Tags