SED: Excluir 4 linhas acima e abaixo de 5 linhas após a correspondência de padrões

7

Eu tenho o arquivo do Hugh com os seguintes detalhes:

define host{
        use                     generic-host            ; Name of host template to use
        host_name               ServerA_172.29.16.102
        alias                   ServerA_172.29.16.102
        address                 172.29.16.102
        check_command           check-host-alive
        max_check_attempts      3
        notification_interval   120
        notification_period     24x7
        }



define host{
        use                     generic-host            ; Name of host template to use
        host_name               ServerB_172.29.16.103
        alias                   ServerB_172.29.16.103
        address                 172.29.16.103
        check_command           check-host-alive
        max_check_attempts      3
        notification_interval   120
        notification_period     24x7
        }

O que eu quero, pesquise "address 172.29.16.102" e exclua 4 linhas acima e depois de 5 linhas.

Eu tentei seguir com o sed mas não funciona

sed '$N;$N;N;/address                 172.29.16.102/,+5d' hosts
    
por Rahul Patil 06.09.2013 / 19:15

6 respostas

7

Se cada seção define_host estiver separada por uma ou mais novas linhas, este é exatamente o tipo de problema que o GNU awk tem. suporte a várias linhas de registro destina-se a resolver

awk -v RS= '!/172.29.16.102/{printf $0""RT}'
    
por 06.09.2013 / 21:26
3

Sempre que vejo esse tipo de pergunta, meu instinto me diz que isso é um trabalho para grep . No entanto, a capacidade de grep de inverter ( -v ) os resultados ao usar o antes & depois que os switches ( -B .. & -A .. ) não permitem isso.

No entanto, essa abordagem inteligente de chamar grep 2 vezes é muito mais simples do que qualquer uma das soluções awk ou sed que eu vi até hoje.

$ grep -v "$(grep -B 4 -A 5 'address 172.29.16.102' <file>)" <file>

Exemplo

Veja alguns dados de amostra.

$ cat sample.txt
define host{
        use                     generic-host            ; Name of host template to use
        host_name               ServerA_172.29.16.102
        alias                   ServerA_172.29.16.102
        address                 172.29.16.102
        check_command           check-host-alive
        max_check_attempts      3
        notification_interval   120
        notification_period     24x7
        }

line1b
line2b
line3b
line4b
address 172.29.16.102
line5a
line4a
line3a
line2a
line1a

define host{
        use                     generic-host            ; Name of host template to use
        host_name               ServerB_172.29.16.103
        alias                   ServerB_172.29.16.103
        address                 172.29.16.103
        check_command           check-host-alive
        max_check_attempts      3
        notification_interval   120
        notification_period     24x7
        }

Agora, quando nós executamos nosso comando:

$ grep -v "$(grep -B 4 -A 5 'address 172.29.16.102' sample.txt)" sample.txt
define host{
        use                     generic-host            ; Name of host template to use
        host_name               ServerA_172.29.16.102
        alias                   ServerA_172.29.16.102
        address                 172.29.16.102
        check_command           check-host-alive
        max_check_attempts      3
        notification_interval   120
        notification_period     24x7
        }


define host{
        use                     generic-host            ; Name of host template to use
        host_name               ServerB_172.29.16.103
        alias                   ServerB_172.29.16.103
        address                 172.29.16.103
        check_command           check-host-alive
        max_check_attempts      3
        notification_interval   120
        notification_period     24x7
        }
    
por 06.09.2013 / 20:32
2

Em vez de usar um número restrito de linhas com sed ou grep, minha preferência seria escrever algo que faça uma interpretação básica do próprio formato de arquivo e avaliar o conteúdo de cada registro . Considere o seguinte usando o awk:

#!/usr/bin/awk -f

function doit(blob) {
  if (blob !~ /address[[:space:]]+172\.29\.16\.102/) {
    print blob;
  } 
}

# Find a new record...
/^define host/ {
  doit(blob);
  blob="";
}

# Don't start each record with a blank line...
length(blob) { blob=blob "\n"; }

# Collect our data...    
{ blob=blob $0; }

END {
  doit(blob);
}

A ideia aqui é que percorreremos o arquivo e, cada vez que vermos define host , começaremos a adicionar cada linha à variável blob . Quando um novo registro é iniciado ou quando chegamos ao final do arquivo, processamos essa variável usando a função doit() .

Isso funciona tanto no GNU quanto no awk clássico.

    
por 06.09.2013 / 20:34
2

Este é um exemplo perfeito do porquê sed é o s tream ed itor, e nunca substituirá o poder de ex para edição de arquivos no local .

ex -c '/address  *172.29.16.103/
?{?,/}/d
x' input

Este comando é uma forma simplificada que não é tão robusta quanto poderia ser, mas serve para ilustração.

O primeiro comando encontra o regex especificado e move o cursor para essa linha.

O segundo comando consiste em dois endereços separados por uma vírgula, sobre os quais o comando d elete é executado. ?{? procura para trás da linha atual por uma chave aberta, e /}/ procura a partir da linha atual por uma chave de fechamento. Tudo no meio é excluído (linewise, então o início da linha de chave aberta também é deletado).

x salva as alterações e sai. E input é, claro, o nome do arquivo.

Para a entrada que você deu, este comando funciona exatamente como desejado.

Agora, eu mencionei que isso poderia ser melhorado consideravelmente. Vamos começar com o regex. A coisa mais óbvia aqui é que os períodos são curingas; o regex como dado também pode coincidir com "172329-16 103". Portanto, os pontos devem ser escapados com barras invertidas, para que correspondam apenas aos períodos literais.

O próximo é o espaço em branco. Eu coloquei dois espaços seguidos por um * (eu poderia ter usado \+ , mas eu não sei se esse recurso é necessário no POSIX), mas e se houver abas no arquivo? A melhor solução é usar [[:space:]] . (Isso seria muito melhor com \+ ; se alguém descobrir se é POSIX, poste um comentário.)

Finalmente, e se a regex não for encontrada no arquivo? Bem, então o arquivo será simplesmente aberto para edição, e o comando "search" irá falhar, e uma mensagem de erro será impressa e o resto dos comandos dados não serão executados - você será deixado no ex editor para que você possa fazer alterações manualmente. No entanto, se você quiser automatizar as edições em um script, provavelmente desejará que o editor saia se nenhuma alteração precisar ser feita. A resposta é usar o comando g lobal e também usar o sinalizador -s para suprimir qualquer saída de ex :

ex -sc 'g/address[[:space:]][[:space:]]*172\.29\.16\.103/ ?{?,/}/d
x' input

Isto não é bastante equivalente ao comando anterior; se houver mais de um bloco de chave com uma linha correspondente, o comando global aqui excluirá todos eles. Isso é provavelmente o que você quer de qualquer maneira.

Se você quiser excluir apenas a primeira correspondência, ao sair sem alterar o arquivo, se não houver nenhuma correspondência, poderá usar o comando x como parte do argumento para o comando g (para sair do comando após o primeiro comando de exclusão ser executado) e lançar um comando q! na parte inferior, caso o comando g não seja executado por falta de linhas correspondentes.

ex -sc 'g/address[[:space:]][[:space:]]*172\.29\.16\.103/ ?{?,/}/d | x
q!' input

Para ser honesto, esses comandos tornam o processo muito mais complicado do que é; a robustez vem à custa da extrema clareza e legibilidade do código. É um trade-off.

Eu recomendo editar alguns arquivos de forma interativa com ex para ter uma ideia. Dessa forma, você pode ver o que está fazendo. Essa sessão de edição em ex para fazer essa correção interativamente se parece com isso:

$ ex input
"input" 23L, 843C
Entering Ex mode.  Type "visual" to go to Normal mode.
:/103
        host_name               ServerB_172.29.16.103
:?{?,/}/d                             # This deletes the current block

:$p                                   # Print and move to last line

:-5,.p                                # Print some more lines to check result
        notification_interval   120
        notification_period     24x7
        }



:?}?+,.d                              # Trim whitespace
        }
:x                                    # Save and exit
$ 

As especificações POSIX para ex fornecem leituras adicionais.

    
por 26.01.2016 / 09:55
0

E sobre isso?

egrep -v '^([[:space:]]+(use|host_name|alias|check_command|max_check_attempts|notification_interval|notification_period)[[:space:]]+|^define host{|^[[:space:]]+})'

Ele remove as linhas específicas que não são necessárias, se supusermos que não há linhas similares no arquivo que não devemos remover.

    
por 07.09.2013 / 04:31
0

Com sed , você pode fazer:

sed -ne'/^[^ ]/!{H;$!d;}' <in >out \
    -e 'x;//{/\n *address  *172\.29\.16\.103/!p;}'

... que armazenará em buffer cada bloco separadamente no espaço H old à medida que forem lidos e imprimirá apenas aqueles que não tiverem correspondência com o padrão.

    
por 26.01.2016 / 15:08