Bash Templating: Как создать файлы конфигурации из шаблонов с помощью Bash?
Я пишу script для автоматизации создания файлов конфигурации для Apache и PHP для своего собственного веб-сервера. Я не хочу использовать графические интерфейсы, такие как CPanel или ISPConfig.
У меня есть несколько шаблонов файлов конфигурации Apache и PHP. Bash script необходимо прочитать шаблоны, сделать замену переменных и выводить анализируемые шаблоны в какую-то папку. Каков наилучший способ сделать это? Я могу думать о нескольких путях. Какой из них лучший, или, может быть, есть некоторые способы сделать это? Я хочу сделать это в чистом Bash (например, это просто в PHP)
1) Как заменить ${} заполнители в текстовом файле?
template.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/"
Мне кажется плохой из-за необходимости избегать множества разных символов и со многими переменными, длина линии будет длиннее.
Можете ли вы подумать о другом элегантном и безопасном решении?
Ответы
Ответ 1
Вы можете использовать это:
perl -p -i -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg' < template.txt
заменить все строки ${...}
соответствующими переменными среды (не забудьте экспортировать их перед запуском этого script).
Для чистого 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 или сохранять количество завершающих строк новой строки. Последний вариант представлен как есть, потому что оболочка "любит" двоичный вход:
-
read
будет интерпретировать обратную косую черту.
-
read -r
не будет интерпретировать обратную косую черту, но все равно опустит последнюю строку, если она не заканчивается новой строкой.
-
"$(…)"
будет выделять столько конечных символов новой строки, сколько присутствует, поэтому я заканчиваю …
с помощью ; echo -n a
и использую echo -n "${line:0:-1}"
: это отбрасывает последний символ (который равен a
) и сохраняет как можно больше конечных новые строки, как на входе (в том числе нет).
Ответ 2
Попробуйте envsubst
FOO=foo
BAR=bar
export FOO BAR
envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF
Ответ 3
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
Ответ 4
Я согласен с использованием sed: это лучший инструмент для поиска/замены. Вот мой подход:
$ cat template.txt
the number is ${i}
the dog 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 name is Fido
Ответ 5
Я думаю, что 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 в микс!
Ответ 6
У меня есть решение bash, такое как mogsie, но с heredoc вместо herestring, чтобы вы не избежали двойных кавычек
eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
Ответ 7
Редактировать 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
Все кредиты Grégory Pakosz.
Ответ 8
Более длинная, но более надежная версия принятого ответа:
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}
к их значениям среды (или, если они undefined, пустая строка).
Он должным образом избегает обратных косых черт и принимает обратную косую черту $, чтобы запретить подстановку (в отличие от envsubst, которая, оказывается, не делает этого).
Итак, если ваша среда:
FOO=bar
BAZ=kenny
TARGET=backslashes
NOPE=engi
и ваш шаблон:
Two ${TARGET} walk into a \\$FOO. \\\\
\\\$FOO says, "Delete C:\\Windows\\System32, it a virus."
$BAZ replies, "\${NOPE}s."
результатом будет:
Two backslashes walk into a \bar. \\
\$FOO says, "Delete C:\Windows\System32, it 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
Ответ 9
Я бы сделал это таким образом, возможно, менее эффективным, но легче читать/поддерживать.
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
Ответ 10
Если вы хотите использовать шаблоны Jinja2, см. этот проект: j2cli.
Он поддерживает:
- Шаблоны из файлов JSON, INI, YAML и входных потоков
- Шаблоны переменных окружения
Ответ 11
Вместо того, чтобы изобретать велосипед, используйте envsubst. Может использоваться практически в любом сценарии, например, для создания файлов конфигурации из переменных среды в контейнерах докера.
Если на Mac убедитесь, что у вас есть homebrew, тогда свяжите его с gettext:
brew install gettext
brew link --force gettext
./template.cfg
# We put env variables into placeholders here
this_variable_1 = ${SOME_VARIABLE_1}
this_variable_2 = ${SOME_VARIABLE_2}
./.env:
SOME_VARIABLE_1=value_1
SOME_VARIABLE_2=value_2
./configure.sh
#!/bin/bash
cat template.cfg | envsubst > whatever.cfg
Теперь просто используйте это:
# make script executable
chmod +x ./configure.sh
# source your variables
. .env
# export your variables
# In practice you may not have to manually export variables
# if your solution depends on tools that utilise .env file
# automatically like pipenv etc.
export SOME_VARIABLE_1 SOME_VARIABLE_2
# Create your config file
./configure.sh
Ответ 12
Принимая ответ от ZyX с использованием чистого bash, но с новым соответствием регулярному выражению стиля и косвенной заменой параметра, он становится:
#!/bin/bash
regex='\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}'
while read line; do
while [[ "$line" =~ $regex ]]; do
param="${BASH_REMATCH[1]}"
line=${line//${BASH_REMATCH[0]}/${!param}}
done
echo $line
done
Ответ 13
Если использовать Perl - это вариант, и вы довольствуетесь базовыми расширениями только переменных окружения (в отличие от всех переменных оболочки), считать Стюарт П. Бентли надежный ответ.
Этот ответ призван обеспечить bash -одно решение, которое, несмотря на использование eval
, должно быть безопасным для использования.
цели:
- Поддержка расширения ссылок
${name}
и $name
.
- Предотвратить все другие расширения:
- подстановки команд (
$(...)
и устаревший синтаксис `...`
)
- арифметические подстановки (
$((...))
и устаревший синтаксис $[...]
).
- Разрешить выборочное подавление расширения переменной путем префикса
\
(\${name}
).
- Сохранять специальные символы. на входе, в частности,
"
и \
экземпляры.
- Разрешить ввод через аргументы или через stdin.
Функция expandVars()
:
expandVars() {
local txtToEval=$* txtToEvalEscaped
# If no arguments were passed, process stdin input.
(( $# == 0 )) && IFS= read -r -d '' txtToEval
# Disable command substitutions and arithmetic expansions to prevent execution
# of arbitrary commands.
# Note that selectively allowing $((...)) or $[...] to enable arithmetic
# expressions is NOT safe, because command substitutions could be embedded in them.
# If you fully trust or control the input, you can remove the `tr` calls below
IFS= read -r -d '' txtToEvalEscaped < <(printf %s "$txtToEval" | tr '`([' '\1\2\3')
# Pass the string to `eval`, escaping embedded double quotes first.
# `printf %s` ensures that the string is printed without interpretation
# (after processing by by bash).
# The `tr` command reconverts the previously escaped chars. back to their
# literal original.
eval printf %s "\"${txtToEvalEscaped//\"/\\\"}\"" | tr '\1\2\3' '`(['
}
<сильные > Примеры:
$ expandVars '\$HOME="$HOME"; `date` and $(ls)'
$HOME="/home/jdoe"; `date` and $(ls) # only $HOME was expanded
$ printf '\$SHELL=${SHELL}, but "$(( 1 \ 2 ))" will not expand' | expandVars
$SHELL=/bin/bash, but "$(( 1 \ 2 ))" will not expand # only ${SHELL} was expanded
- По соображениям производительности функция автоматически считывает вход stdin в память, но легко адаптирует функцию к линейному подходу.
- Также поддерживает неосновные расширения переменных, такие как
${HOME:0:10}
, если они не содержат встроенной команды или арифметических подстановок, таких как ${HOME:0:$(echo 10)}
- Такие внедренные подстановки фактически BREAK функции (потому что все экземпляры
$(
и `
слепо экранированы).
- Аналогично, неправильные ссылки на переменные, такие как
${HOME
(отсутствует закрытие }
) BREAK функция.
- Из-за bash обработки строк с двойными кавычками обратные косые черты обрабатываются следующим образом:
-
\$name
предотвращает расширение.
- Единственный
\
, за которым не следует $
, сохраняется как есть.
- Если вы хотите представлять несколько соседних экземпляров
\
, вы должны их удвоить; например.:
-
\\
→ \
- то же самое, что только \
-
\\\\
→ \\
- Ввод не должен содержать следующие (редко используемые) символы, которые используются для внутренних целей:
0x1
, 0x2
, 0x3
.
- В значительной степени гипотетическая проблема заключается в том, что если bash должен ввести новый синтаксис расширения, эта функция может не помешать таким расширениям - см. ниже решение, которое не использует
eval
.
Если вы ищете более ограничительное решение, которое поддерживает только расширения ${name}
- то есть с обязательными фигурными фигурными скобками, игнорируя ссылки $name
- см. мой ответ.
Вот улучшенная версия bash -одно, eval
-бесплатное решение из принятого ответа:
Улучшения:
- Поддержка расширения ссылок
${name}
и $name
.
- Поддержка
\
-escaping ссылок переменных, которые не следует расширять.
- В отличие от вышеприведенного решения
eval
- неосновные расширения игнорируются
- неверные ссылки на ссылки игнорируются (они не нарушают script)
IFS= read -d '' -r lines # read all input from stdin at once
end_offset=${#lines}
while [[ "${lines:0:end_offset}" =~ (.*)\$(\{([a-zA-Z_][a-zA-Z_0-9]*)\}|([a-zA-Z_][a-zA-Z_0-9]*))(.*) ]] ; do
pre=${BASH_REMATCH[1]} # everything before the var. reference
post=${BASH_REMATCH[5]}${lines:end_offset} # everything after
# extract the var. name; it in the 3rd capture group, if the name is enclosed in {...}, and the 4th otherwise
[[ -n ${BASH_REMATCH[3]} ]] && varName=${BASH_REMATCH[3]} || varName=${BASH_REMATCH[4]}
# Is the var ref. escaped, i.e., prefixed with an odd number of backslashes?
if [[ $pre =~ \\+$ ]] && (( ${#BASH_REMATCH} % 2 )); then
: # no change to $lines, leave escaped var. ref. untouched
else # replace the variable reference with the variable value using indirect expansion
lines=${pre}${!varName}${post}
fi
end_offset=${#pre}
done
printf %s "$lines"
Ответ 14
Здесь другое чистое решение bash:
- используя heredoc, так:
- сложность не увеличивается из-за дополнительного требуемого синтаксиса
- может содержать bash код
- который также позволяет вам делать отступы. См. Ниже.
- он не использует eval, поэтому:
- нет проблем с отображением завершающих пустых строк
- проблем с кавычками в шаблоне
$ cat code
#!/bin/bash
LISTING=$( ls )
cat_template() {
echo "cat << EOT"
cat "$1"
echo EOT
}
cat_template template | LISTING="$LISTING" bash
$ cat template
(с завершающими символами новой строки и двойными кавычками)
<html>
<head>
</head>
<body>
<p>"directory listing"
<pre>
$( echo "$LISTING" | sed 's/^/ /' )
<pre>
</p>
</body>
</html>
Выход
<html>
<head>
</head>
<body>
<p>"directory listing"
<pre>
code
template
<pre>
</p>
</body>
</html>
Ответ 15
На этой странице описывается ответ с awk
awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt
Ответ 16
Идеальный случай для shtpl. (проект моего, так что он не широко используется и отсутствует в документации. Но вот решение, которое оно предлагает в любом случае. Можете ли вы его протестировать.)
Просто выполните:
$ i=1 word=dog sh -c "$( shtpl template.txt )"
Результат:
the number is 1
the word is dog
Удачи.
Ответ 17
Вот еще одно решение: сгенерируйте скрипт bash со всеми переменными и содержимым файла шаблона, этот скрипт будет выглядеть так:
word=dog
i=1
cat << EOF
the number is ${i}
the word is ${word}
EOF
Если мы будем кормить этот скрипт в bash, он даст желаемый результат:
the number is 1
the word is dog
Вот как сгенерировать этот скрипт и передать этот скрипт в bash:
(
# Variables
echo word=dog
echo i=1
# add the template
echo "cat << EOF"
cat template.txt
echo EOF
) | bash
обсуждение
Ответ 18
Вы также можете использовать bashible (который внутренне использует подход оценки, описанный выше/ниже).
Есть пример, как создать HTML из нескольких частей:
https://github.com/mig1984/bashible/tree/master/examples/templates
Ответ 19
# Usage: template your_file.conf.template > your_file.conf
template() {
local IFS line
while IFS=$'\n\r' read -r line ; do
line=${line//\\/\\\\} # escape backslashes
line=${line//\"/\\\"} # escape "
line=${line//\`/\\\`} # escape `
line=${line//\$/\\\$} # escape $
line=${line//\\\${/\${} # de-escape ${ - allows variable substitution: ${var} ${var:-default_value} etc
# to allow arithmetic expansion or command substitution uncomment one of following lines:
# line=${line//\\\$\(/\$\(} # de-escape $( and $(( - allows $(( 1 + 2 )) or $( command ) - UNSECURE
# line=${line//\\\$\(\(/\$\(\(} # de-escape $(( - allows $(( 1 + 2 ))
eval "echo \"${line}\"";
done < "$1"
}
Это чистая функция bash, настраиваемая по своему вкусу, используемая в производстве и не должна прерываться при вводе.
Если он сломается - сообщите мне.
Ответ 20
Здесь функция bash, которая сохраняет пробелы:
# Render a file in bash, i.e. expand environment variables. Preserves whitespace.
function render_file () {
while IFS='' read line; do
eval echo \""${line}"\"
done < "${1}"
}
Ответ 21
Здесь приведено изменение perl
script на основе нескольких других ответов:
perl -pe 's/([^\\]|^)\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}/$1.$ENV{$2}/eg' -i template
Возможности (основанные на моих потребностях, но их легко изменить):
- Пропускает экранированные расширения параметров (например,\${VAR}).
- Поддерживает разложение параметров формы ${VAR}, но не $VAR.
- Заменяет ${VAR} пустой строкой, если нет VAR-envar.
- Поддерживает только символы a-z, A-Z, 0-9 и подчеркивания в имени (исключая цифры в первой позиции).
Ответ 22
Посмотрите на скрипт Python для подстановки простых переменных здесь: https://github.com/jeckep/vsubst
Это очень просто использовать:
python subst.py --props secure.properties --src_path ./templates --dst_path ./dist
Ответ 23
Для одного подхода к шаблонам см. мой ответ здесь.