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