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}'
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
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}'
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>
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
}
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.
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.
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.
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.
Tags text-processing sed