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

10
grep -A 2 -B 3 

imprime 2 linhas depois da seqüência do grep e imprime 3 linhas antes.

grep -C 3

imprime 3 linhas antes e 3 linhas depois

Infelizmente, o grep que estou usando não suporta essas opções. Existe algum comando alternativo ou script disponível para simular isso? Usando sed / awk / perl / scripts de shell?

    
por Prashant Bhate 12.04.2011 / 21:35

6 respostas

6

Uma maneira moderadamente feia de fazer isso é

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

ou substitua -c por -C NUM por NUM linhas de contexto. Vai produzir saída extra, no entanto. (Se o seu diff suportar -u / -U NUM , será mais limpo.)

Se o seu diff não tiver -c / -C / -u , ainda existem maneiras de fazer isso, mas eles são bem feios. Por outro lado, um sistema cujo diff nem mesmo suporta -c provavelmente não tem Perl.

    
por 12.04.2011 / 21:45
5

ack requer apenas Perl e inclui as opções -A , -B e -C que funcionam como o grep. Ele usa a sintaxe regex do Perl ao invés do grep, e a maneira como ele seleciona os arquivos para pesquisar é bem diferente. Você pode querer experimentar a opção -f ao usá-la (que imprime os arquivos que ela irá procurar sem realmente pesquisar nada).

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

    
por 12.04.2011 / 22:32
5

Este script perl simples emula grep -A até 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> 
    
por 12.04.2011 / 21:50
3

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

Se você preferir usar ferramentas padrão e um pouco de script, veja um awk script que emula o comportamento das opções -A e -B do GNU grep. 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

Execute como grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTER , em que PATTERN é o padrão a ser pesquisado (um estendido expressão regular com algumas adições ao awk ) e NBEFORE e NAFTER são os números de linhas a serem impressas antes e depois de uma correspondência, respectivamente (com o padrão 0). Exemplo:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar'
    
por 12.04.2011 / 22:40
2

Acontece que é bastante complicado emular -B, por causa dos problemas que surgem quando você tem linhas correspondentes seguindo uma a outra diretamente. Isso praticamente não permite o uso de qualquer tipo de varredura de arquivo single-pass-through.

Eu 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á mais ou menos corretamente como grep -A7-B3, com a advertência descrita no primeiro parágrafo.

Uma solução alternativa (também de arquivo único) para esse problema é usar o perl para alimentar uma cadeia de comando:

sed -n 'perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file' file
    
por 12.04.2011 / 22:18
0

Usando sed , você pode primeiro obter os números de linha das linhas correspondentes, decremento & incremente um número de linha em um loop while e, em seguida, use sed -n "n1,n2p" para imprimir linhas de contexto principal ( n1 ) e final ( n2 ) (semelhante à alternativa sed sugerida pelo usuário455). Muitos processos de leitura podem levar a um impacto no desempenho.

ed pode referenciar diretamente as linhas anteriores e seguintes de uma linha correspondente, mas falhará se o intervalo de linhas especificado não existir; por exemplo, a linha correspondente é a linha número 2, mas 5 linhas pré-correspondência devem ser impressas. Usando ed , é necessário adicionar um número apropriado de linhas (vazias) no início e no final. (Para arquivos enormes ed pode não ser a ferramenta certa, consulte: bfs - scanner de arquivo grande ).

# 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
    
por 19.04.2013 / 16:10