Existe uma maneira fácil de fazer o equivalente a 'sed…', substituindo a mesma linha por múltiplos valores?

5

Eu tenho um arquivo onde eu quero substituir algumas variáveis com dados de um script de shell.

-A INPUT -i lo -s @LOCAL_IP@ -j ACCEPT

Aqui eu quero substituir @ LOCAL_IP @ por um endereço IP, eu uso o seguinte:

... | sed -e 's/@LOCAL_IP@/192.168.1.1/' | ...

Eu posso ter 192.168.1.1 em uma variável no meu shell script, mas isso é para dar uma idéia básica.

Agora, tenho outra regra parecida com esta:

-A INPUT -i @PUBLIC_INTERFACE@ -p tcp -m tcp --dport 22
            -d @PUBLIC_IP@ -s @ADMIN_IPS@ --syn -j ACCEPT

(esta é uma linha longa, dividida aqui por legibilidade)

O arquivo de entrada real deve ter muitas linhas como essa no arquivo de entrada, a maioria sem o @ADMIN_IPS@ , por exemplo:

-A INPUT -i @PUBLIC_INTERFACE@ -p tcp -m tcp --dport 80 -d @PUBLIC_IP@ --syn -j ACCEPT
-A INPUT -i @PUBLIC_INTERFACE@ -p tcp -m tcp --dport 22 -d @PUBLIC_IP@ -s @ADMIN_IPS@ --syn -j ACCEPT
-A INPUT -i @PUBLIC_INTERFACE@ -p tcp -m tcp --dport 443 -d @PUBLIC_IP@ --syn -j ACCEPT

Neste caso, posso facilmente substituir @ PUBLIC_INTERFACE @ e @ PUBLIC_IP @. Isso é exatamente o mesmo que a linha anterior. No entanto, @ ADMIN_IPS @ é uma lista de 2 ou 3 endereços IP. Qual deve ser o resultado é algo assim:

-A INPUT -i eth0 -p tcp -m tcp --dport 80 -d 8.8.8.8 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.1 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.2 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.3 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 80 -d 8.8.8.8 --syn -j ACCEPT

Existe uma maneira relativamente fácil de transformar uma única linha em várias linhas com uma ferramenta como sed no Linux? (Ubuntu 16.04) Nesse caso, os três ADMIN_IPS endereços seriam definidos em uma variável como em:

ADMIN_IPS=8.8.10.1,8.8.10.2,8.8.10.3

Pode ser um espaço separado ou até mesmo um array.

    
por Alexis Wilke 06.07.2016 / 21:10

4 respostas

3

Aqui está uma solução awk feia; outra pessoa pode ter uma maneira mais inteligente de fazer isso:

awk -v pi=1.2.3.4 -v pint=eth0 -v pip=8.8.8 -vaips="8.8.10.1 8.8.10.2 8.8.10.3"  \
  'BEGIN{
        split(aips, array)
   }
   {
        gsub(/@PUBLIC_INTERFACE@/, pint);
        gsub(/@PUBLIC_IP@/, pi);
        if (/@ADMIN_IPS@/) {
                copy=$0
                for (i in array)  {
                  gsub(/@ADMIN_IPS@/, array[i], copy)
                  print copy
                  copy=$0
                }
        } else {
          print;
        }
   }' input

Isto passa as variáveis para o awk (usando a separação de espaços para os IPs admins); antes que qualquer entrada seja lida, o awk dividirá o "array" passado em um array awk genuíno chamado array . Para cada linha de entrada, o awk pesquisará globalmente & substitua as várias variáveis singleton, então se @ ADMIN_IPS @ estiver presente na linha, passe por um loop sobre o IP do administrador e imprima cópias da linha de entrada com o IP do administrador correspondente substituído.

    
por 06.07.2016 / 22:14
2

Por favor, tente :::::

given::
echo $ADMIN_IPS 

8.8.10.1,8.8.10.2,8.8.10.3

e arquivo de entrada / tmp / myvar dado com o conteúdo como abaixo ::::

-A INPUT -i @ PUBLIC_INTERFACE @ -p tcp -m tcp --dportar 80 -d @ PUBLIC_IP @ --syn -j ACCEPT

-A ENTRADA -i @ PUBLIC_INTERFACE @ -p tcp -m tcp --dportar 22 -d @ PUBLIC_IP @ -s @ ADMIN_IPS @ --syn -j ACEITAR

-A INPUT -i @ PUBLIC_INTERFACE @ -p tcp -m tcp --dportar 443 -d @ PUBLIC_IP @ --syn -j ACCEPT

while read j; do savedline=$(echo "$j"|sed 's/@PUBLIC_INTERFACE@/eth0/'|sed 's/@PUBLIC_IP@/1.1.1.1/'); for i in $(echo $ADMIN_IPS |tr ',' '\n'); do echo $savedline|sed "s/@ADMIN_IPS@/$i/";done|uniq;done < /tmp/myvar

Experimente e nos informe.

    
por 06.07.2016 / 21:46
1

Aqui está uma solução perl usando Text::Template . O script lê o modelo a partir de uma variável de string ( $tstr ), executa todas as substituições (incluindo algum código perl embutido para executar um loop sobre a matriz @ADMIN_IPS e depois imprime o resultado:

(em um sistema debian, isso requer que o pacote libtext-template-perl seja instalado)

#! /usr/bin/perl

use strict;
use Text::Template;

# The template, in a string ($tstr):
my $tstr='-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 80 -d {$PUBLIC_IP} --syn -j ACCEPT

{
  foreach $a (@ADMIN_IPS) {
    $OUT .= "-A INPUT -i $PUBLIC_INTERFACE -p tcp -m tcp --dport 22 -d $PUBLIC_IP -s $a --syn -j ACCEPT\n";
  }
}
-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 443 -d {$PUBLIC_IP} --syn -j ACCEPT
';

# create the Text::Template object ($tt)
my $tt = Text::Template->new(TYPE => 'STRING', SOURCE => $tstr);

# define a hash reference to hold all the replacements:
my $vars = { PUBLIC_INTERFACE => 'eth0',
             PUBLIC_IP => '8.8.8.8',
             ADMIN_IPS => [ '8.8.10.1', '8.8.10.2', '8.8.10.3' ],
           };

# fill in the template
my $text = $tt->fill_in(HASH => $vars);

print $text;

Saída:

-A INPUT -i eth0 -p tcp -m tcp --dport 80 -d 8.8.8.8 --syn -j ACCEPT

-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.1 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.2 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.3 --syn -j ACCEPT

-A INPUT -i eth0 -p tcp -m tcp --dport 443 -d 8.8.8.8 --syn -j ACCEPT

Para a maioria dos modelos de luz como este, as variáveis escalar (valor único) e a lista ocasional (também conhecida como matriz) ou hash (matriz associativa) são tudo o que você precisa.

O script acima pode ser adaptado indefinidamente para todos os tipos de trabalhos semelhantes - basta alterar o modelo e a referência hash $vars . E não é de todo difícil, por exemplo, carregar o template de um arquivo e as variáveis (escalares e arrays) de outro, então você pode ter um pequeno script reutilizável que aceita dois argumentos de arquivo (template e vars).

Além do próprio modelo e da configuração $vars , há apenas cerca de cinco linhas de código. Talvez 6 ou 7, dependendo de como você conta o loop for no template.

Modelos podem ser lidos a partir de um nome de arquivo, um array de linhas, um identificador de arquivo (incl. stdin) ou uma string como neste exemplo.

Você pode fazer cálculos, pesquisas de tabela, consultas de banco de dados (por exemplo, com o módulo DBI ), extração de dados de um arquivo CSV, buscar e processar a saída de sub-rotinas e programas externos e mais dentro de um modelo. Qualquer coisa que você possa fazer com perl .

Para variáveis escalares simples, tudo o que você precisa fazer é incorporar o nome da variável no modelo dentro de chaves (por exemplo, com a variável $foo , use {$foo} ). Para matrizes, hashes e cálculos, etc., você precisaria incorporar algum código perl no modelo.

Aqui está uma versão que lê um nome de arquivo de modelo e um nome de arquivo de variável de configuração dos dois primeiros argumentos na linha de comando:

(em um sistema debian, isso requer que os pacotes libconfig-simple-perl e libtext-template-perl sejam instalados)

#! /usr/bin/perl

use strict;
use Text::Template;
use Config::Simple;

# create the Text::Template object ($tt)
my $tt = Text::Template->new(TYPE => 'FILE', SOURCE => $ARGV[0]);

# read the config file into the '%vars' hash.    
my $cfg = new Config::Simple();
$cfg->read($ARGV[1]);
my %vars = $cfg->vars();

# strip "default." from key names.
%vars = map { s/^default\.//r => $vars{$_} } keys(%vars);

# fill in the template
my $text = $tt->fill_in(HASH => \%vars);

print $text;

NOTA: o script precisa remover default. do início de cada nome de chave hash porque o arquivo de configuração é muito parecido com um arquivo .INI e pode ter [sections] como eles. Qualquer variável de configuração que não esteja em uma seção é considerada na seção default . Escrever o modelo com variáveis como {$default.PUBLIC_INTERFACE} seria entediante, então a solução é corrigir as chaves do %vars hash.

BTW, estes% de[sections] semelhantes a .INI não são necessariamente um problema. É possível fazer bom uso deles em um modelo. Mas o prefixo default. é inútil e irritante quando usado com Text::Template .

De qualquer forma, com este arquivo de modelo:

$ cat iptables.tpl 
-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 80 -d {$PUBLIC_IP} --syn -j ACCEPT
{
  my @o=();
  foreach $a (@ADMIN_IPS) {
    push @o, "-A INPUT -i $PUBLIC_INTERFACE -p tcp -m tcp --dport 22 -d $PUBLIC_IP -s $a --syn -j ACCEPT";
    $OUT .= join("\n",@o);
  }
}
-A INPUT -i {$PUBLIC_INTERFACE} -p tcp -m tcp --dport 443 -d {$PUBLIC_IP} --syn -j ACCEPT

OBSERVAÇÃO: esse modelo foi um pouco melhor, pois usa uma matriz ( @o ) e join() para evitar a adição de novas linhas indesejadas - você percebeu como "trapaceei" em $tstr na primeira versão, adicionando um mais nova linha de modo que as linhas de saída da matriz estivessem em um parágrafo separado?

e este arquivo contendo as variáveis:

$ cat iptables.var 
PUBLIC_INTERFACE=eth0
PUBLIC_IP=8.8.8.8
ADMIN_IPS=8.8.10.1, 8.8.10.2, 8.8.10.3

Esse arquivo funcionaria exatamente da mesma maneira se tivesse inserido [default] como a primeira linha.

Além disso, ao contrário da maioria das formas de arquivos .ini, este tem uma maneira muito fácil de definir variáveis de matriz: basta separá-las com uma vírgula, com espaços em branco extras opcionais, exatamente o que você pediu em sua pergunta. p>

BTW, o espaço em branco é ignorado nas definições de variáveis, a menos que você as coloque entre aspas. Veja man Config::Simple para mais detalhes sobre o (s) arquivo (s) de configuração.

Execute assim:

$ ./alexis.pl iptables.tpl iptables.var 
-A INPUT -i eth0 -p tcp -m tcp --dport 80 -d 8.8.8.8 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.1 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.2 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 8.8.10.3 --syn -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 443 -d 8.8.8.8 --syn -j ACCEPT

É intencionalmente minimalista (ou seja, muito rápida e suja, por exemplo, nem sequer tenta validar a existência dos arquivos) e há muitas maneiras de melhorar, mas é um exemplo totalmente funcional de como fazer o básico trabalho.

    
por 07.07.2016 / 15:00
0

Se você tiver uma chance de anexar toda a linha para cada ip, posso sugerir o seguinte

#!/bin/bash

first="-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 10.2.2.1 --syn -j ACCEPT"
second="-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 10.2.2.2 --syn -j ACCEPT"
third="-A INPUT -i eth0 -p tcp -m tcp --dport 22 -d 8.8.8.8 -s 10.2.2.3 --syn -j ACCEPT"

sed 's/.*@ADMIN_IPS@.*/${first}\n${second}\n${third}/g' file

Pode não ser o mais limpo, mas pode ser uma solução

experimente

    
por 06.07.2016 / 21:43