Шаблоны Bash: Как создавать файлы конфигурации из шаблонов с помощью Bash? - PullRequest
107 голосов
/ 26 мая 2010

Я пишу скрипт для автоматизации создания файлов конфигурации для Apache и PHP для моего собственного веб-сервера.Я не хочу использовать какие-либо графические интерфейсы, такие как CPanel или ISPConfig.

У меня есть несколько шаблонов конфигурационных файлов Apache и PHP.Bash-скрипт должен читать шаблоны, производить подстановку переменных и выводить проанализированные шаблоны в какую-то папку.Каков наилучший способ сделать это?Я могу придумать несколько способов.Какой из них лучший или может быть есть лучшие способы сделать это?Я хочу сделать это в чистом Bash (это легко в PHP, например)

1) Как заменить $ {} заполнителей в текстовом файле?

шаблон.txt:

the number is ${i}
the word is ${word}

script.sh:

#!/bin/sh

#set variables
i=1
word="dog"
#read in template one line at the time, and replace variables
#(more natural (and efficient) way, thanks to Jonathan Leffler)
while read line
do
    eval echo "$line"
done < "./template.txt"

Кстати, как мне перенаправить вывод во внешний файл?Нужно ли что-то экранировать, если переменные содержат, скажем, кавычки?

2) Использование cat & sed для замены каждой переменной ее значением:

Данный template.txt:

The number is ${i}
The word is ${word}

Команда:

cat template.txt | sed -e "s/\${i}/1/" | sed -e "s/\${word}/dog/"

Мне кажется плохим из-за необходимости экранировать много разных символов, и со многими переменными строка будет слишком длинной.

Можете ли вы вспомнить некоторыедругое элегантное и безопасное решение?

Ответы [ 22 ]

109 голосов
/ 15 июня 2012

Попробуйте envsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF
56 голосов
/ 26 мая 2010

Вы можете использовать это:

perl -p -i -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg' < template.txt

для замены всех ${...} строк соответствующими переменными окружения (не забудьте экспортировать их перед запуском этого скрипта).

Для чистого bash это должно работать (при условии, что переменные не содержат $ {...} строк):

#!/bin/bash
while read -r line ; do
    while [[ "$line" =~ (\$\{[a-zA-Z_][a-zA-Z_0-9]*\}) ]] ; do
        LHS=${BASH_REMATCH[1]}
        RHS="$(eval echo "\"$LHS\"")"
        line=${line//$LHS/$RHS}
    done
    echo "$line"
done

. Решение, которое не зависает, если RHS ссылается на некоторую переменную, которая ссылается на себя:

#!/bin/bash
line="$(cat; echo -n a)"
end_offset=${#line}
while [[ "${line:0:$end_offset}" =~ (.*)(\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})(.*) ]] ; do
    PRE="${BASH_REMATCH[1]}"
    POST="${BASH_REMATCH[4]}${line:$end_offset:${#line}}"
    VARNAME="${BASH_REMATCH[3]}"
    eval 'VARVAL="$'$VARNAME'"'
    line="$PRE$VARVAL$POST"
    end_offset=${#PRE}
done
echo -n "${line:0:-1}"

ПРЕДУПРЕЖДЕНИЕ : Я не знаю, как правильно обрабатывать ввод с NUL в bash или сохранять количество завершающих строк новой строки. Последний вариант представлен как есть, потому что оболочка «любит» двоичный ввод:

  1. read будет интерпретировать обратную косую черту.
  2. read -r не будет интерпретировать обратную косую черту, но все равно отбросит последнюю строку, если она не заканчивается новой строкой.
  3. "$(…)" уберет столько конечных строк, сколько есть, поэтому я заканчиваю с ; echo -n a и использую echo -n "${line:0:-1}": это удаляет последний символ (a) и сохраняет столько же конечных переводы строк, как было во вводе (в том числе нет).
36 голосов
/ 17 августа 2012

envsubst был новым для меня. Фантастическая.

Для справки, использование heredoc - отличный способ создать шаблон файла conf.

STATUS_URI="/hows-it-goin";  MONITOR_IP="10.10.2.15";

cat >/etc/apache2/conf.d/mod_status.conf <<EOF
<Location ${STATUS_URI}>
    SetHandler server-status
    Order deny,allow
    Deny from all
    Allow from ${MONITOR_IP}
</Location>
EOF
31 голосов
/ 26 мая 2010

Я согласен с использованием sed: это лучший инструмент для поиска / замены. Вот мой подход:

$ cat template.txt
the number is ${i}
the dog's name is ${name}

$ cat replace.sed
s/${i}/5/
s/${name}/Fido/

$ sed -f replace.sed template.txt > out.txt

$ cat out.txt
the number is 5
the dog's name is Fido
19 голосов
/ 14 сентября 2012

Я думаю, что eval работает очень хорошо. Он обрабатывает шаблоны с переносами строк, пробелами и всевозможными вещами Bash. Если у вас есть полный контроль над самими шаблонами, конечно:

$ cat template.txt
variable1 = ${variable1}
variable2 = $variable2
my-ip = \"$(curl -s ifconfig.me)\"

$ echo $variable1
AAA
$ echo $variable2
BBB
$ eval "echo \"$(<template.txt)\"" 2> /dev/null
variable1 = AAA
variable2 = BBB
my-ip = "11.22.33.44"

Конечно, этот метод следует использовать с осторожностью, поскольку eval может выполнять произвольный код. Запуск этого от имени root в значительной степени исключен. Кавычки в шаблоне необходимо экранировать, иначе они будут съедены eval.

Вы также можете использовать здесь документы, если предпочитаете от cat до echo

$ eval "cat <<< \"$(<template.txt)\"" 2> /dev/null

@ plockc предоставил решение, позволяющее избежать проблемы с выходом из цитаты bash:

$ eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

Редактировать: Удалена часть о запуске этого как root с использованием sudo ...

Редактировать: Добавлен комментарий о том, как нужно избегать кавычек, добавлено решение plockc в смесь!

18 голосов
/ 10 июня 2013

У меня есть решение для bash, такое как mogsie, но с heredoc вместо herestring, чтобы избежать двойных кавычек

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
16 голосов
/ 02 февраля 2014

Редактировать 6 января 2017

Мне нужно было сохранить двойные кавычки в моем файле конфигурации, чтобы двойные экранирование двойных кавычек с помощью sed помогло:

render_template() {
  eval "echo \"$(sed 's/\"/\\\\"/g' $1)\""
}

Я не могу думать о продолжении новых строк, но пустые строки между ними сохраняются.


Хотя это старая тема, IMO я нашел более элегантное решение здесь: http://pempek.net/articles/2013/07/08/bash-sh-as-template-engine/

#!/bin/sh

# render a template configuration file
# expand variables + preserve formatting
render_template() {
  eval "echo \"$(cat $1)\""
}

user="Gregory"
render_template /path/to/template.txt > path/to/configuration_file

Все кредиты Грегори Пакош .

9 голосов
/ 29 июля 2014

Более длинная, но более надежная версия принятого ответа:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})?;substr($1,0,int(length($1)/2)).($2&&length($1)%2?$2:$ENV{$3||$4});eg' template.txt

Это расширяет все экземпляры $VAR или ${VAR} до их значений среды (или, если они не определены, пустой строки).

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

Итак, если ваша среда:

FOO=bar
BAZ=kenny
TARGET=backslashes
NOPE=engi

и ваш шаблон:

Two ${TARGET} walk into a \\$FOO. \\\\
\\\$FOO says, "Delete C:\\Windows\\System32, it's a virus."
$BAZ replies, "\${NOPE}s."

результат будет:

Two backslashes walk into a \bar. \\
\$FOO says, "Delete C:\Windows\System32, it's a virus."
kenny replies, "${NOPE}s."

Если вы хотите избежать обратной косой черты до $ (вы можете написать «C: \ Windows \ System32» в шаблоне без изменений), используйте эту слегка измененную версию:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\});substr($1,0,int(length($1)/2)).(length($1)%2?$2:$ENV{$3||$4});eg' template.txt
8 голосов
/ 28 мая 2010

Я бы сделал это таким образом, возможно, менее эффективно, но легче читать / поддерживать.

TEMPLATE='/path/to/template.file'
OUTPUT='/path/to/output.file'

while read LINE; do
  echo $LINE |
  sed 's/VARONE/NEWVALA/g' |
  sed 's/VARTWO/NEWVALB/g' |
  sed 's/VARTHR/NEWVALC/g' >> $OUTPUT
done < $TEMPLATE
8 голосов
/ 25 июня 2014

Если вы хотите использовать Jinja2 шаблоны, см. Этот проект: j2cli .

Поддерживает:

  • Шаблоны из файлов JSON, INI, YAML и входных потоков
  • Создание шаблонов из переменных среды
...