有没有一种简单的方法可以相当于“sed ...”用多个值替换同一行?

有没有一种简单的方法可以相当于“sed ...”用多个值替换同一行?

我有一个文件,我想替换其中的一些内容变量来自 shell 脚本的数据。

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

这里我想用IP地址替换@LOCAL_IP@,我使用以下内容:

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

我的 shell 脚本中的变量中可能有 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 等工具将单行转换为多行sed? (Ubuntu 16.04) 在这种情况下,这三个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哈希引用。例如,从一个文件加载模板并从另一个文件加载变量(标量和数组)并不困难,因此您可以拥有一个带有两个文件参数(模板和变量)的小型可重用脚本。

除了模板本身和$vars设置之外,只有大约 5 行代码。可能是 6 或 7,具体取决于您如何计算for模板中的循环。

可以从文件名、行数组、已打开的文件句柄(包括标准输入)或字符串中读取模板,如本例所示。

您可以在模板内执行计算、表查找、数据库查询(例如使用模块DBI)、从 CSV 文件中提取数据、获取和处理子例程和外部程序的输出等。你可以用 做任何事perl

对于简单的标量变量,您所需要做的就是将变量名称嵌入到模板中的大括号内(例如使用variable $foo,use {$foo})。对于数组、哈希和计算等,您需要perl在模板中嵌入一些代码。


这是一个从命令行上的前两个参数读取模板文件名和配置变量文件名的版本:

(在 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

它可能不是最干净的,但可以是一个解决方案

试一下

相关内容