Есть ли простой способ сделать эквивалент `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@ без @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 

Есть ли относительно простой способ преобразования одной строки в несколько строк с помощью такого инструмента, как sed в Linux? (Ubuntu 16.04) В этом случае три адреса ADMIN_IPS будут определены в переменной, как в:

 ADMIN_IPS=8.8.10.1,8.8.10.2,8.8.10.3 

Это может быть разделение пространства или даже массив.

4 Solutions collect form web for “Есть ли простой способ сделать эквивалент `sed …` заменяя одну и ту же строку несколькими значениями?”

Вот уродливое решение 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 разделит этот переданный «массив» на массив bona fide awk с именем array . Для каждой строки ввода awk будет глобально искать и заменять различные одноэлементные переменные, тогда, если в строке присутствует @ ADMIN_IPS @, пройдите через петлю по IP-адресам администратора и распечатайте копии строки ввода с соответствующими правами администратора заменены.

Пожалуйста попробуйте:::::

 given:: echo $ADMIN_IPS 

8.8.10.1,8.8.10.2,8.8.10.3

и входной файл / tmp / myvar с содержимым, как показано ниже :::

-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

 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 

Попробуйте и сообщите нам.

Вот решение 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 

Для большинства легких шаблонов, таких как скалярные (однозначные) переменные и случайный список (aka array) или хэш (aka ассоциативный массив) – это все, что вам нужно.

Вышеупомянутый скрипт может быть адаптирован бесконечно для всех видов подобных заданий – просто измените шаблон и $vars хэш- $vars . И совсем не сложно, например, загрузить шаблон из одного файла и переменные (скаляры и массивы) из другого, так что у вас может быть крошечный перезаписываемый скрипт, который принимает два аргумента файла (шаблон и vars).

Помимо самого шаблона и установки $vars существует только около 5 строк кода. Возможно, 6 или 7, в зависимости от того, как вы рассчитываете цикл for в шаблоне.

Шаблоны можно читать из имени файла, массива строк, уже открытого файла-дескриптора (включая stdin) или строки, как в этом примере.

Вы можете выполнять вычисления, поиск таблиц, запросы к базе данных (например, с помощью модуля DBI ), извлечение данных из файла CSV, выборку и обработку выходных данных подпрограмм и внешних программ и многое другое в шаблоне. Все, что вы можете сделать с perl .

Для простых скалярных переменных все, что вам нужно сделать, это вставить имя переменной в шаблон внутри фигурных скобок (например, с переменной $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 в первой версии, добавив еще одну $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 

Этот файл будет работать точно так же, если он был вставлен в качестве первой строки.

Кроме того, в отличие от большинства форм .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 

Он преднамеренно минималистский (т.е. очень быстрый и грязный, например, даже не пытается проверить наличие файлов), и есть много способов его улучшения, но это полнофункциональный пример того, как выполнять основную работу.

Если у вас есть возможность подключить всю строку для каждого 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 

Это может быть не самое чистое, но может быть решением

попробуй

  • Как я могу найти и заменить только в том случае, если совпадение составляет целое слово?
  • присоединяйте два файла на основе столбца, когда в сценарии bash нет взаимно однозначного соответствия (awk, grep, sed)
  • Регулярные команды и команды с командой sed
  • Поиск файла для строки, окруженной двумя другими известными строками?
  • sed: многострочная замена блока конфигурации
  • Как удалить все числа, окруженные <>
  • Как я могу прокручивать строки файла и находить файлы, соответствующие каждой строке?
  • чтение списка строк из файла, записывающего вывод в утилиту
  • Использование 'sed' для поиска и замены
  • Проблема удаления / замены специального символа °
  • Как читать несколько строк вместо строк
  • Linux и Unix - лучшая ОС в мире.