同じ行を複数の値に置き換えるために「sed ...」と同じことを行う簡単な方法はありますか?

同じ行を複数の値に置き換えるために「sed ...」と同じことを行う簡単な方法はありますか?

ファイルがありますが、その内容の一部を変更したいと思います。変わりやすいシェルスクリプトのデータ。

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

ここでは@LOCAL_IP@をIPアドレスに置き換えたいと思います。以下を使用します。

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

おそらく、シェルスクリプトの変数に192.168.1.1があります。しかし、これは単なる基本的なアイデアを提供することです。

今、次のような別のルールがあります。

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

(これは長い行であり、読みやすくするためにここでは壊れています。)

実際の入力ファイルには、入力ファイルの内容に似た行がたくさん含まれていますが、ほとんどはそうではありません@ADMIN_IPS@。たとえば、次のようになります。

-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

この場合、@PUBLIC_INTERFACE@と@PUBLIC_IP@を簡単に置き換えることができます。これは前の行とまったく同じです。ただし、@ADMIN_IPS@は2〜3個のIPアドレスのリストです。結果は次のようになります。

-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

Linuxなどのツールを使用して1行を複数行に変換する比較的簡単な方法はありますかsed? (Ubuntu 16.04)この場合、これら3つのADMIN_IPSアドレスは次のように変数で定義されます。

ADMIN_IPS=8.8.10.1,8.8.10.2,8.8.10.3

スペースで区切ったり、配列にすることもできます。

答え1

これは醜いawkソリューションです。他の人はこれを行うより賢い方法を持つことができます。

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

これは変数をawkに渡します(管理IPのスペース区切りを使用)。 awkは、入力を読み取る前に着信「配列」を名前付きの実際のawk配列に分割しますarray。各入力行に対して、awk はさまざまなシングルトン変数をグローバルに検索して置き換えます。その後、@ADMIN_IPS@が行にある場合は、管理IPを繰り返し、対応する管理IPの置き換えで入力行のコピーを印刷します。 。

答え2

試してみてください:::::

given::
echo $ADMIN_IPS 

8.8.10.1,8.8.10.2,8.8.10.3

入力ファイル/tmp/myvarの内容は次のとおりです::::

-A 入力 -i @PUBLIC_INTERFACE@ -p tcp -m tcp --dport 80 -d @PUBLIC_IP@ --syn -j

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

-A 入力 -i @PUBLIC_INTERFACE@ -p tcp -m tcp --dport 443 -d @PUBLIC_IP@ --syn -j

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

私たちに教えてください。

答え3

perlここで使用されているソリューションがありますText::Template。スクリプトは文字列変数()からテンプレートを読み取り、$tstrすべての置換を実行します(配列を繰り返す一部の組み込みPerlコードを含む)。@ADMIN_IPS次に結果を印刷します。

(Debianシステムではlibtext-template-perlパッケージをインストールする必要があります)

#! /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;

出力:

-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

このようなほとんどの軽量テンプレートでは、スカラー(単一値)変数と、時々使用されるリスト(配列とも呼ばれます)またはハッシュ(関連付け配列とも呼ばれます)で十分です。

上記のスクリプトは、さまざまな同様の操作に無限に適用できます。テンプレートと$varsハッシュ参照のみを変更するだけです。たとえば、あるファイルからテンプレートをロードし、別のファイルから変数(スカラーと配列)をロードすることは難しくないため、2つのファイルパラメータ(テンプレートと変数)を持つ小さな再利用可能なスクリプトを持つことができます。

テンプレート自体と$vars設定を除いて、コードは約5行です。forテンプレートのループ数を計算する方法に応じて、6または7にすることができます。

この例に示すように、テンプレートはファイル名、行配列、開いたファイルハンドル(標準入力を含む)、または文字列から読み取ることができます。

テンプレート内で計算、テーブルルックアップ、データベースクエリー(モジュールの使用などDBI)、CSVファイルからのデータ抽出、サブルーチン、および外部プログラムの出力のインポートと処理などを実行できます。あなたはそれを何でも使うことができますperl

単純なスカラー変数の場合は、テンプレートの中かっこ内に変数名を挿入するだけです(例:using Variable $foo、use {$foo})。配列、ハッシュ、計算などの場合は、perlテンプレートにいくつかのコードを含める必要があります。


以下は、コマンドラインの最初の2つの引数からテンプレートファイル名と構成変数ファイル名を読み取るバージョンです。

(Debianシステムではインストールlibconfig-simple-perlとパッケージが必要です)libtext-template-perl

#! /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;

注:構成ファイルにはファイルと非常によく似たファイルがある可能性があるため、default.各ハッシュキー名の先頭からスクリプトを削除する必要があります。セクションにないすべての構成変数は、そのセクションにあると見なされます。このような変数を使用してテンプレートを作成するのは面倒なので、解決策はハッシュのキーを変更することです。.INI[sections]default{$default.PUBLIC_INTERFACE}%vars

しかし、これらの.INI形式のファイルが[sections]必ずしも問題になるわけではありません。テンプレートからこれを最大限に活用できます。ただしdefault.、と一緒に使用すると、接頭辞は意味がなく、迷惑ですText::Template

とにかく、次のテンプレートファイルを使用してください。

$ 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

注:このテンプレートは配列(@o)を使用し、望ましくない改行を追加しないという点でわずかに改善されました。最初のバージョンでは、配列が段落線を別の場所に出力するように改行を追加して、「トリック」を使用した方法にjoin()気づきましたか?$tstr

ファイルには次の変数が含まれています。

$ 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

[default]ファイルを最初の行に挿入すると、まったく同じように動作します。

さらに、ほとんどの.INIファイル形式とは異なり、このファイルには配列変数を定義する非常に簡単な方法があります。カンマで区切り、オプションで余分なスペースを使用できます。これは質問で要求されたものとまったく同じです。

ただし、変数定義のスペースは、引用符で囲まない限り無視されます。man Config::Simple詳しくは、構成ファイルを参照してください。

次のように実行します。

$ ./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

これは意図的にミニマリストであり(つまり、非常に高速で汚れています。たとえば、ファイルの存在を確認しようとはしません)、これを改善する方法はたくさんありますが、基本的な作業を完了する方法の完全な機能的な例です。

答え4

各IPにすべての行を追加する機会がある場合は、次のことを提案できます。

#!/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

最もきれいではありませんが、解決策になる可能性があります。

試してみてください

関連情報