Existe alguma alternativa aos comutadores -A -B -C do grep (para imprimir algumas linhas antes e depois)?

10
grep -A 2 -B 3 

imprime 2 linhas após a sequência grep e imprime 3 linhas antes.

grep -C 3

imprime 3 linhas antes e 3 linhas depois

Infelizmente, o grepque estou usando não suporta essas opções. Existem comandos ou scripts alternativos disponíveis para simular isso? Usando scripts sed/ awk/ perl/ shell?

Prashant Bhate
fonte
+1 Eu não sabia que havia um -Cinterruptor.
Lazer
1
Instale o grep do GNU. De maneira mais geral, quando uma nova máquina Sun chegou aqui, o primeiro passo na configuração foi o que alguém chamou GNU > /usr/local. Os programas GNU têm muitas extensões muito úteis e são projetados para evitar restrições arbitrárias (mas você paga muito caro em tamanho e, às vezes, desempenho). Muitos sistemas proprietários possuem repositórios de pacotes "não oficiais" com o GNU e outras ferramentas. O "parceiro" não vai falar sobre eles, mesmo quando eles são gerenciados pelo fornecedor ...
vonbrand

Respostas:

6

Uma maneira moderadamente feia de fazer isso é

grep -v pattern file >file.tmp; diff -c file.tmp file

ou substitua -cpor -C NUMpara NUMlinhas de contexto. Porém, produzirá uma saída extra. (Se o seu diffsuporte -u/ -U NUM, será mais limpo.)

Se o seu diffnão tiver -c/ -C/ -u, ainda existem maneiras de fazê-lo, mas são muito feias. Por outro lado, um sistema diffque nem suporta -cprovavelmente também não possui Perl.

geekosaur
fonte
Isso é legal, funciona como charme, embora eu tivesse que usar a opção -bitw com isso para fazê-lo funcionar com arquivos gerados pelo Windows.
Prashant Bhate
Você pode enviar stdin para diff, e pular o temporária:grep -v pattern file | diff -c - file
Cascabel
5

ack requer apenas Perl, e inclui -A, -Be -Copções que funcionam como grep do. Ele usa a sintaxe regex do Perl em vez do grep, e a maneira como ele seleciona os arquivos a serem pesquisados ​​é bem diferente. Você pode tentar a -fopção ao usá-la (que imprime os arquivos que serão pesquisados ​​sem realmente pesquisar nada).

Ele pode ser instalado como um único script que não requer módulos não essenciais. Basta soltá-lo em seu ~/bindiretório (ou em qualquer outro lugar do seu PATH ao qual você tenha acesso de gravação) e verifique se ele é chmodexecutável.

cjm
fonte
Sua caixa de produção e, infelizmente, eu não tenho privilégios suficientes para instalar qualquer coisa, e eu não posso arriscar, porém, obrigado por esta dica vou instalá-lo e tentar no meu laptop em casa
Prashant Bhate
@Prashant, você não precisa de raiz para instalar ackpara seu próprio uso.
CJM
Sim, mas ainda não posso usá-lo lá, embora a sua certeza de que este roteiro vai ficar para sempre no meu ~ / bin :)
Prashant Bhate
@ Pashant: Por que você não pode usá-lo? É apenas um script perl.
intuído
1
Na sua caixa de PRODUÇÃO, é necessário obter aprovações especiais de permissões bla bla bla ... fazer qualquer coisa nele. e alguma coisa der errado lá no vem na minha cabeça;) e não vale a pena :)
Prashant Bhate
5

Este script perl simples emula grep -Aaté certo ponto

#!/usr/bin/perl

$pattern=shift; #patthern to search
$lines=shift; # number of lines to print

$n = 0;
while (<>) {
  $n = $lines if /$pattern/; # reset counting
  if ($n) { print; $n-- } # print if within
  $n = 0 if eof; # don't leak across file boundaries
}

Observe que você pode adicionar uma declaração de uso, para tornar o script legível e utilizável;)

USAGE:    $./grep-A.pl <pattern> <numLines> <filename> 
Vijay Anant
fonte
Bom, qual versão do perl eu preciso para executar isso?
Prashant Bhate
Eu uso a v5.10.1, acho que o perl 5 é bastante comum atualmente.
Vijay Anant
ya sua 5.8.8 e funciona, ótimo, mas eu preciso de um script que faz o -B fazer
Prashant Bhate
Boa. Eu mudaria a ordem dos argumentos; grep-A 3 fooparece muito mais natural do que grep-A foo 3. :-) #
82813 musiphil
3

Você pode simplesmente instalar o GNU grep ou o Ack (escrito em Perl, entende muitas das opções do GNU grep e muito mais).

Se você preferir seguir as ferramentas padrão mais um pouco de script, aqui está um script awk que emula o comportamento das opções -Ae grep do GNU -B. Minimamente testado.

#!/bin/sh
# grep-ac: a grep-like awk script
# Arguments: pattern = awk regexp to search for
#            before = number of lines to print before a match
#            after = number of lines to print after a match
{ "exec" "awk" "-f" "$0" "$@"; }
# The array h contains the history of lines that haven't been printed
# but are eligible for being "before" lines.
# The variable until contains the number of the last "after" line to print.
match($0, pattern) {   # the current line matches
    for (i in h) {
        print h[i];    # print each remaining before line
        delete h[i];   # delete each line as it's printed
    }
    until=NR+after;    # record the last after line to print
}
{
    if (NR<=until) print $0;    # from a match to its last after line: print
    else h[NR]=$0;              # after that: save in history
    delete h[NR-before];        # remove line too old to be a before line
}
END {exit !until}               # exit status: 0 if there was a match, else 1

Executá-lo como grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTERonde PATTERNé o padrão para procurar (uma expressão regular estendida com algumas adições awk ), e NBEFOREe NAFTERsão os números de linhas a serem impressas antes e depois de uma partida, respectivamente (inadimplente a 0). Exemplo:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar'
Gilles 'SO- parar de ser mau'
fonte
Qualquer solução que armazena dados na matriz está fora de questão ... como mencionei anteriormente, o tamanho do arquivo é enorme e pode exceder o limite. O awk também neste sistema não permite tamanho de arquivo superior a 3000 bytes.
Prashant Bhate
2
@ Pashant: Eu não entendo suas objeções. Esse script exclui as linhas quando elas não estiverem qualificadas para serem as linhas de antes. Ele não usa mais memória do que é inerentemente necessário, considerando os requisitos, exceto que o awk pode ter uma sobrecarga maior do que um programa para fins especiais (mas menor que o Perl, que você também está considerando). O tamanho total do arquivo é completamente irrelevante.
Gilles 'SO- stop be evil' -
2
{ "exec" "awk" "-f" "$0" "$@"; }: maneira muito bacana de contornar as limitações na análise de linha shebang.
dubiousjim
2

Acontece que é bastante difícil imitar -B, devido aos problemas que surgem quando as linhas correspondentes são seguidas diretamente. Isso praticamente não permite o uso de qualquer tipo de verificação de arquivo de passagem única.

Percebi isso enquanto brincava com a seguinte aproximação:

perl -pe 'if(/search_term/) {print foreach @A; print ">"; $B=4}; shift @A if push(@A, $_)>7; $_ = "" unless ($B-- > 0);' target_file

Isso funcionará aproximadamente corretamente, como faria o grep -A7 -B3, com a ressalva descrita no primeiro parágrafo.

Uma solução alternativa (também de arquivo único) para esse problema é usar o perl para alimentar uma sequência de comandos:

sed -n `perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file` file
user455
fonte
oneliner bastante longo, mas, esse arquivo é muito grande, portanto, empurrar as linhas para a matriz nesse caso é uma má idéia, não é?
Prashant Bhate
O shift @A if push(@A,$_)>7;bit mantém apenas uma matriz de tamanho máximo 7. (esse é o seu parâmetro -A). A segunda opção mantém um arquivo incrivelmente pequeno (basta executar o perl sem a camada externa sed para ver o que é gerado lá), mas ele lê o arquivo duas vezes.
usar o seguinte comando
0

Usando sedvocê, você pode obter primeiro o número de linhas correspondentes, diminuir e incrementar um determinado número de linha em um whileloop e depois usá-lo sed -n "n1,n2p"para imprimir linhas dos contextos inicial ( n1) e final ( n2) (semelhante à sedalternativa sugerida pelo usuário455). Muitos processos de leitura podem levar a um impacto no desempenho.

edpode referenciar diretamente as linhas anteriores e seguintes de uma linha correspondente, mas falha se o intervalo de linhas especificado não existir; por exemplo, a linha correspondente é a linha número 2, mas 5 linhas anteriores à correspondência devem ser impressas. Usando ed-o, por conseguinte, é necessário adicionar um número adequado de linhas (vazio) no início e no final. (No edentanto, para arquivos grandes, pode não ser a ferramenta certa, consulte: bfs - scanner de arquivos grandes ).

# sample code to match lines with number 5 plus previous & following line
# (using Bash)
printf '%s\n' {1..20} > num.txt

# sed
sed -n '/5/=' num.txt | while read num; do
   n1=$((num - 1))
   n2=$((num + 1))
   [[ $n1 -lt 1 ]] && n1=1
   sed -n "${n1},${n2}p" num.txt
   echo --
done | sed -e '${/^--$/d;}'

# ed
cat <<-'EOF' | ed -s num.txt | sed -e $'N;N;a\\\n--' | sed -e '${/^--$/d;}'
H
0i
beginning: added line one
.
$a
end: added line one
.
,g/5/km\
'm-1,'m+1p
q
EOF
Larz
fonte