Encontre todas as ocorrências em um arquivo com o sed

15

Usando o SO OPEN STEP 4.2 ... No momento, estou usando o seguinte sedcomando:

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

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

Dale
fonte
1
Por favor, sempre incluir o seu OS. As soluções geralmente dependem do sistema operacional que está sendo usado. Você está usando Unix, Linux, BSD, OSX, outra coisa? Qual versão?
terdon
GRANDE PONTO! O uso do Open Step versão 4.2 é bastante antigo e os shells incluídos não incluem muitos dos recursos mencionados nas respostas abaixo.
Dale
Por curiosidade - o que é um sistema OPEN STEP 4.2 e para que é usado hoje?
Thorbjørn Ravn Andersen
(e se o Perl estiver disponível, você pode realmente fazer muitas coisas legais com isso) #
Thorbjørn Ravn Andersen
@ ThorbjørnRavnAndersen Talvez seja isso: en.wikipedia.org/wiki/OpenStep
Barmar

Respostas:

4

Aqui está uma tentativa de emular grep -B3usando uma janela móvel sed, com base neste exemplo GNU sed (mas esperamos que seja compatível com POSIX - com reconhecimento a @ 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 lide com o caso de borda em que há menos de 3 linhas do contexto anterior antes da primeira correspondência. A expressão do meio (correspondência de expressão regular) imprime uma linha na parte superior da janela até que o texto da correspondência desejada seja exibido no buffer do padrão. A final $!N;Drola a janela em uma linha, exceto quando atinge o final da entrada.

chave de aço
fonte
-enão é específico do GNU. Para ser POSIX / portátil, você precisa , pois não pode haver nada depois }(e você precisa de um ;antes).
Stéphane Chazelas
Obrigado @ StéphaneChazelas - então você está dizendo que, para ser POSIX / portátil, o primeiro grupo precisa ser dividido / modificado como -e '1h;2,4{H;g;}' -e '1,3d'? Eu não tenho um sistema não-GNU para testar (e o --posixcomutador GNU sed parece não se importar).
Steeldiver
1
Sim, no Linux, você pode testar uma implementação diferente com o sedbaú de ferramentas da herança, que é um descendente do tradicional Unix sed. A especificação POSIX / Unix para sedé a pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
Stéphane Chazelas
Estou recebendo um evento não encontrado em nenhum destes itens: N; D ': evento não encontrado. Estou sentindo falta de sintaxe em algum lugar? Obrigado!!
Dale
Desculpe, acabei de perceber que minha edição mais recente omitiu uma citação simples após a primeira expressão -e. Eu o corrigi agora - você pode tentar novamente com a expressão acima, por favor?
Steeldriver
10

grep fará um trabalho melhor disso:

grep -B 3 141.299.99.1 TESTFILE

Os -B 3meios para imprimir as três linhas antes de cada partida. Isso será impresso --entre cada grupo de linhas. Para desativar isso, use --no-group-separatortambém.

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

Michael Homer
fonte
1
Michael Homer - Obrigado. Eu não tenho a opção - B. Alguma outra ideia?
Dale
@Dale Você pode instalar o GNU grep? Isso lhe dará a opção.
Barmar
9

Com sedvocê pode fazer uma janela deslizante.

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

Isso faz. Mas cuidado - basho comportamento insano de se expandir ! mesmo quando citado !!! na cadeia de comando do seu histórico de comandos pode torná-lo um pouco louco. Prefixe o comando com set +H;se você achar que este é o caso. Para reativá-lo (mas por que ???), faça set -Hdepois.

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

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

[2addr]N Anexe a próxima linha de entrada, menos sua linha de \new final , ao espaço do padrão, usando uma \nlinha de ew incorporada para separar o material anexado do material original. Observe que o número da linha atual é alterado.

[2addr]P Escreva o espaço do padrão, até a primeira linha de \new, na saída padrão.

[2addr]D Exclua o segmento inicial do espaço do padrão pela primeira linha de \new e inicie o próximo ciclo.

Portanto, na primeira linha, você adiciona uma linha extra ao espaço do padrão, para que fique assim:

^line 1s contents\nline 2s contents$

Em seguida, na primeira linha e em todas as linhas posteriores - exceto a última -, você adiciona outra linha ao espaço do padrão. Então fica assim:

^line 1\nline 2\nline 3$

Se o seu endereço IP for encontrado dentro de você, você será Pdirecionado para a primeira nova linha, então apenas a linha 1 aqui. No final de cada ciclo, você Delimina o mesmo e recomeça com o que resta. Portanto, 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 desses três, o mais antigo será impresso - sempre. Então você está sempre apenas três linhas à frente.

Aqui está um exemplo rápido. Vou imprimir um buffer de três linhas 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

Isso é um pouco mais complicado do que o seu caso, porque eu tive que alternar entre a 0\nnova linha ou o 0$fim do espaço do padrão para se parecer mais com o seu problema - mas eles são sutilmente diferentes, pois isso requer uma âncora - o que pode ser um pouco difícil, pois o espaço-padrão muda constantemente.

Usei os casos ímpares de 10 e 52 para mostrar que, desde que a âncora seja flexível, o mesmo ocorre com a saída. Totalmente portável, posso obter os mesmos resultados contando com o algoritmo e:

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 para duas.

Enfim, você entendeu a idéia.

mikeserv
fonte
Obrigado por todo o seu trabalho duro. Desculpe, onde colocaria o nome do arquivo pelo qual gostaria de pesquisar?
Dale
@ Dale - meu mal. sed '...' $filename. A propósito - deixei os períodos da sua própria sequência de pesquisa, mas na verdade não são períodos em um padrão - eles representam qualquer caractere único. Provavelmente, você deve evitá oct\.oct\.oct\.oct-los para que correspondam apenas a períodos.
mikeserv
Tentei brincar com ele e com diferentes símbolos <> e recebo um evento não encontrado, que recebo com outras soluções aqui, então, me pergunto se meu sistema operacional não é compatível com essas soluções.
Dale
agora resulta com -> N; /141.299.99.1/P; D ': evento não encontrado.
Dale
@ Dale - por favor, veja a atualização. Isso deve ajudá-lo.
mikeserv
4

Como você menciona que não tem a -Bopção grep, é possível usar o 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 awk.

Joseph R.
fonte
Não tenho certeza se minha versão do Perl suporta isso, mas vou tentar. Muito obrigado por responder a minha pergunta - muito grato!
Dale
@ Dale Você é muito bem-vindo. Duvido que este código faça uso de qualquer recurso Perl de ponta.
Joseph R.
4

Quando disponível, você pode usar o pcregrep :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file
caos
fonte
Verificando se eu tenho o PCREGREP. Eu gosto da compacidade do comando. Muito grato pelo seu tempo e esforços. Obrigado!!!
Dale
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 suporte =~):

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 agrupar o arquivo inteiro 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 
terdon
fonte
Meu shell é muito antigo - Steve Jobs Open Step. Ótima idéia e obrigado pelo seu tempo !!! Dale
Dale
@ Dale, a abordagem perl funcionará em qualquer lugar. Diga-nos o seu sistema operacional (adicione-o à sua pergunta) para sugerir coisas que funcionarão para você.
terdon
Se eu copiar o seu Perl e colocá-lo no Bloco de Notas e colocá-lo em uma linha, ele funcionará! Pergunta - se eu quisesse, digamos 10 linhas antes do padrão de correspondência, onde eu mudaria de 3 para 10? Obrigado!
Dale
Vejo que posso adicionar mais linhas adicionando mais instruções $ F [$ iX]. Obrigado!
Dale
4

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

ack -B 3 141.299.99.1 file

ack é uma ferramenta como grep, otimizada para programadores.

cuonglm
fonte
Eu gosto da compactação do comando, mas meu sistema não suporta ack ao procurar nas páginas de manual. Ótima idéia e muito obrigado pelo seu tempo !!! Dale
Dale
@Dale: Surpreendente! Qual é o seu sistema operacional? Se você tiver perl, você pode usar ack.
cuonglm
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

Nisso awk solução, é usada uma matriz que sempre conterá 3 linhas antes do padrão atual. Portanto, quando o padrão é correspondido, o conteúdo da matriz, juntamente 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
Ramesh
fonte
tão detalhado - muito obrigado. Vou dar uma chance. Muito grato pelo seu tempo !! Dale
Dale
Eu tenho um arquivo de teste e sua solução funciona! O problema, porém, é que, quando eu o executo no meu grande arquivo de produção, ele volta com o Número de registro muito longo, para que a saída não possa funcionar com o comando. Meu comando original no topo desta página funciona, mas encontra apenas uma instância. Eu aprecio sua ajuda. Existe algo que eu possa fazer com o meu comando original para que ele encontre mais de uma instância?
Dale
1

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

Usando /141[.]299[.]99[.]1/é mais seguro, e você pode adicionar contexto adicional no início e no final de todo o regexp para certificar-se ele não corresponder 3141., .12, .104, etc.

user117529
fonte
1
Esse é um bom ponto - e eu também considerei. Ainda assim, usei a string fornecida pelo solicitante como uma partida de trabalho conhecida - e o notifiquei pessoalmente da mesma quando oferecida a oportunidade. De qualquer forma - nem todas essas - a resposta da steeldriver citou a partida de char desde o início.
mikeserv