Encontre todas as ocorrências em um arquivo com sed

14

Usando o OPEN STEP 4.2 OS ... No momento, estou usando o seguinte comando sed :

sed -n '1,/141.299.99.1/p' TESTFILE | tail -3

Este comando encontrará uma instância em um arquivo com o ip de 141.299.99.1 e também incluirá 3 linhas antes dele, o que é bom, com a exceção de que eu também gostaria de encontrar todas as instâncias do IP e do 3 linhas antes e não apenas o primeiro.

    
por Dale 24.07.2014 / 00:26

9 respostas

4

Aqui está uma tentativa de emular grep -B3 usando uma janela de movimento sed, com base em este arquivo GNU sed exemplo (mas esperamos que seja compatível com POSIX - com o reconhecimento de @ StéphaneChazelas):

sed -e '1h;2,4{;H;g;}' -e '1,3d' -e '/141\.299\.99\.1/P' -e '$!N;D' file

As duas primeiras expressões preparam um buffer de padrão de várias linhas e permitem que ele manipule o caso de borda no qual há menos de 3 linhas do contexto anterior antes da primeira correspondência. A expressão do meio (correspondência de regex) imprime uma linha na parte superior da janela até que o texto da correspondência desejada seja preenchido pelo buffer padrão. O% final $!N;D rola a janela em uma linha, exceto quando ela atinge o final da entrada.

    
por 24.07.2014 / 03:56
10

grep fará um trabalho melhor:

grep -B 3 141.299.99.1 TESTFILE

O -B 3 means para imprimir as três linhas antes de cada partida. Isso imprimirá -- entre cada grupo de linhas. Para desabilitar isso, use --no-group-separator também.

A opção -B é suportada pelo GNU grep e a maioria das versões do BSD também ( OSX , FreeBSD , OpenBSD , NetBSD ), mas tecnicamente não é uma opção padrão.

    
por 24.07.2014 / 00:39
9

Com sed você pode fazer uma janela deslizante.

sed '1N;$!N;/141.299.99.1/P;D'

Isso faz isso. Mas cuidado - o comportamento insano de bash de expandir ! mesmo quando citado !!! na sequência de comandos do histórico de comandos pode deixá-lo um pouco louco. Prefixe o comando com set +H; se você achar que é esse o caso. Para reativá-lo (mas por que ???) faça set -H depois.

Isso, é claro, só se aplicaria se você estivesse usando bash - embora eu não acredite que você esteja. Eu sou bastante certo que você está trabalhando com csh - (que é o shell cujo comportamento insano bash emula com a expansão do histórico, mas talvez não para os extremos que o shell c levou) . Então, provavelmente , um \! deve funcionar. Eu espero.

É tudo código portátil: POSIX descreve seus três operadores: (embora Vale a pena notar que eu só confirmei que esta descrição existia em 2001)

[2addr]N Append the next line of input, less its terminating \newline, to the pattern space, using an embedded \newline to separate the appended material from the original material. Note that the current line number changes.

[2addr]P Write the pattern space, up to the first \newline, to standard output.

[2addr]D Delete the initial segment of the pattern space through the first \newline and start the next cycle.

Portanto, na primeira linha, você adiciona uma linha extra ao espaço do padrão, por isso é assim:

^line 1s contents\nline 2s contents$

Então, na primeira linha e em todas as linhas posteriores - exceto a última - você adiciona outra linha ao espaço padrão. Então parece com isso:

^line 1\nline 2\nline 3$

Se o seu endereço IP for encontrado dentro de você P rint até a primeira nova linha, apenas a linha 1 aqui. No final de cada ciclo você D elete mesmo e começar de novo com o que resta. Então, o próximo ciclo se parece com:

^line 2\nline 3\nline 4$

... e assim por diante. Se o seu ip for encontrado em qualquer um dos três, o mais antigo será impresso - todas as vezes. Então você está sempre somente três linhas adiante.

Aqui está um exemplo rápido. Eu vou ter um buffer de três linhas impresso para cada número que termina em zero:

seq 10 52 | sed '1N;$!N;/0\(\n\|$\)/P;D'

10
18
19
20
28
29
30
38
39
40
48
49
50

Esse é um pouco mais complicado que o seu caso porque eu tive que alternar entre 0\n newline ou 0$ end do espaço de padrões para parecer mais próximo do seu problema - mas eles são sutilmente diferentes porque isso requer uma âncora - o que pode ser um pouco difícil de fazer, já que o padrão espacial muda constantemente.

Eu usei os casos ímpares de 10 e 52 para mostrar que, contanto que a âncora seja flexível, a saída também é. Totalmente portável, posso obter os mesmos resultados contando com o algoritmo e fazendo:

seq 10 52 | sed '1N;$!N;/[90]\n/P;D'

E amplie a pesquisa enquanto restringe minha janela - de 0 a 9 e 0 e de 3 linhas a duas.

De qualquer forma, você tem a ideia.

    
por 24.07.2014 / 02:15
4

Desde você menciona que você não tem a opção -B para grep , você pode usar Perl (por exemplo) para fazer uma janela deslizante de 4 linhas:

perl -ne '
    push @window,$_;
    shift @window if @window > 4;
    print @window if /141\.299\.99\.1/
' your_file

A resposta de Ramesh faz uma coisa semelhante com awk .

    
por 24.07.2014 / 01:29
4

Quando disponível, você pode usar pcregrep :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file
    
por 24.07.2014 / 01:55
4

Você pode implementar a mesma abordagem básica que as outras respostas não-grep no próprio shell (isso pressupõe um shell relativamente recente que suporta =~ ):

while IFS= read -r line; do 
    [[ $line =~ 141.299.99.1 ]] && printf "%s\n%s\n%s\n%s\n" $a $b $c $line;
    a=$b; b=$c; c=$line; 
done < file 

Como alternativa, você pode fazer o slurp de todo o arquivo em uma matriz:

perl -e '@F=<>; 
        for($i=0;$i<=$#F;$i++){
          print $F[$i-3],$F[$i-2],$F[$i-1],$F[$i] if $F[$i]=~/141.299.99.1/
        }' file 
    
por 24.07.2014 / 02:11
4

Se o seu sistema não suporta grep context, você pode tentar ack-grep :

ack -B 3 141.299.99.1 file

ack é uma ferramenta como o grep, otimizada para programadores.

    
por 24.07.2014 / 03:18
2
awk '/141.299.99.1/{for(i=1;i<=x;)print a[i++];print} {for(i=1;i<x;i++)
     a[i]=a[i+1];a[x]=$0;}'  x=3 filename

Nesta solução awk , é usada uma matriz que sempre conterá 3 linhas antes do padrão atual. Portanto, quando o padrão é correspondido, o conteúdo da matriz junto com o padrão atual é impresso.

Teste

-bash-3.2$ cat filename
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.5
10.0.0.6
10.0.0.7
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
10.0.0.17
10.0.0.18
10.0.0.19

Depois de executar o comando, a saída é

10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
    
por 24.07.2014 / 01:49
1

Na maioria deles, /141.299.99.1/ também corresponderá (por exemplo) 141a299q99+1 ou 141029969951 porque . em uma expressão regular pode representar qualquer caractere.

Usar /141[.]299[.]99[.]1/ é mais seguro e você pode adicionar contexto adicional no início e no final de todo o regexp para garantir que ele não corresponda a 3141. , .12 , .104 , etc.

    
por 24.07.2014 / 21:18

Tags