Bash Templating: Як створити файли конфігурації з шаблонів за допомогою Bash?


134

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

У мене є деякі шаблони файлів конфігурації Apache та PHP. Сценарій Bash повинен читати шаблони, робити змінну підстановку та виводити розбір шаблонів у якусь папку. Який найкращий спосіб зробити це? Я можу придумати кілька способів. Який із них найкращий, а може бути, є кілька кращих способів зробити це? Я хочу це зробити в чистому 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"

BTW, як я перенаправляю висновок на зовнішній файл тут? Чи потрібно щось уникати, якщо змінні містять, скажімо, лапки?

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/"

Мені здається погано через те, що потрібно уникати багатьох різних символів, і з багатьма змінними лінія буде занадто довгою.

Чи можете ви придумати якесь інше елегантне та безпечне рішення?


Відповіді:


61

Ви можете скористатися цим:

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 в басі або зберегти кількість останніх рядків. Останній варіант представлений таким чином, оскільки оболонки «люблять» бінарний вхід:

  1. read буде інтерпретувати зворотні риси.
  2. read -r не інтерпретуватиме косої риски, але все одно викине останній рядок, якщо він не закінчується новим рядком.
  3. "$(…)"буде смуга , як багато хвостових символів нового рядка , як там присутній, так що в кінцевому з ; echo -n aі використанням echo -n "${line:0:-1}": це краплі останнього символу (який a) і зберігають як багато хвостових символів нового рядка , оскільки не було на вході ( в тому числі немає).

3
Я б змінив [^}]на [A-Za-Z_][A-Za-z0-9_]bash-версію, щоб запобігти виходу оболонки за межі суворої заміни (наприклад, якщо вона намагалася обробити ${some_unused_var-$(rm -rf $HOME)}).
Кріс Джонсен

2
@FractalizeR ви можете змінити $&рішення perl на "": перший залишається ${...}недоторканим, якщо він піддається заміні, другий замінює його порожнім рядком.
ZyX

5
ПРИМІТКА. Мабуть, відбулася зміна з bash 3.1 на 3.2 (і вище), в якій одиничні лапки навколо регулярного вираження - трактують вміст регулярного вираження як літеральний рядок. Отже, вищевказаний вираз повинен бути ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/…
Блакитні води

2
Щоб whileцикл читав останній рядок, навіть якщо він не закінчується новим рядком, використовуйте while read -r line || [[ -n $line ]]; do. Крім того, ваша readкомандна смужка веде і пробілює пробіли з кожного рядка; щоб уникнути цього, використовуйтеwhile IFS= read -r line || [[ -n $line ]]; do
mklement0

2
Зазначимо лише обмеження для тих, хто шукає комплексне рішення: ці \ інакші зручні рішення не дозволяють вибірково захищати змінні посилання від розширення (наприклад, шляхом їх набору).
mklement0

138

Спробуйте envsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF

12
Для довідки, envsubstне потрібно використовувати heredoc, оскільки bash трактує heredoc як буквальний рядок з подвійним цитуванням і інтерполює в нього змінні. Це прекрасний вибір, коли ви хочете прочитати шаблон з іншого файлу. Хороша заміна набагато громіздкішим m4.
beporter

2
Я був дуже приємно здивований, дізнавшись про цю команду. Я намагався зрубати функціональність envsubst вручну з нульовим успіхом. Спасибі йоттаца!
Тім Стюарт

4
Примітка: envsubstце утиліта gettext GNU і фактично не все надійна (оскільки gettext призначений для локалізації людських повідомлень). Найголовніше, що він не розпізнає замінені $ {VAR} заміни, котрі протікають косою рисою (тому у вас не може бути шаблону, який використовує підстановки $ VAR під час виконання, як скрипт оболонки або конф-файл Nginx). Дивіться мою відповідь щодо рішення, яке обробляє втечу зворотної косої риси.
Стюарт П. Бентлі

4
@beporter У цьому випадку, якщо ви хочете з певних причин передати цей шаблон envsubst, ви хочете скористатися <<"EOF", що не інтерполює змінні (цитовані термінатори - це як одноцитати гередок).
Стюарт П. Бентлі

2
Я використовував це так: cat template.txt | envsubst
truthadjustr

47

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

Для запису використання heredoc є прекрасним способом шаблонування конф-файлів.

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

1
Я вважаю за краще це краще, ніж envsubstcoz, це врятувало мене від додаткового apt-get install gettext-baseв моєму Dockerfile
truthadjustr

Оболонка як сценарій, схожий на шаблони, проте без будь-якої зовнішньої установки бібліотеки та стресу від впорання з хитрими виразами.
千 木 郷

35

Я згоден з використанням 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

1
Для цього потрібен тимчасовий файл для рядка заміни, правда? Чи можна це зробити без тимчасових файлів?
Владислав Раструсний

@FractalizeR: Деякі версії sed мають -iопцію (редагувати файли на місці), подібну до параметра perl . Перевірте, чи немає на вашій сідалці сторінки .
Кріс Джонсен

@FractalizeR Так, sed -i замінить inline. Якщо вам подобається Tcl (інша мова сценаріїв), то перегляньте цю тему: stackoverflow.com/questions/2818130/…
Хай Ву

Я створив substitu.sed з файлів властивостей з наступною командою sed: sed -e 's / ^ / s \ / $ {/ g' -e 's / = /} \ // g' -e 's / $ / \ // g 'the.properties> substitu.sed
Jaap D

Код @hai vu створює програму sed і передає програму, використовуючи прапор sed -f. Якщо ви хочете, ви можете замість цього передати кожен рядок програми sed в sed, використовуючи прапорці -e. FWIW Мені подобається ідея використання sed для шаблонування.
Ендрю Олбрайт

23

Я думаю, що eval працює дуже добре. Він обробляє шаблони з рядками, пробілами та всілякими штучками. Якщо ви маєте повний контроль над самими шаблонами, звичайно:

$ 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 запропонував рішення, яке дозволяє уникнути проблематики випуску цитати:

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

Редагувати: Видалена частина про запуск цього файлу як root за допомогою sudo ...

Редагувати: Додано коментар про те, як потрібно уникнути цитат, додавши до суміші рішення plockc!


Ця смужка цитат у вашому шаблоні і не замінює всередині одиничних лапок, тому залежно від формату шаблону, це може призвести до тонких помилок. Це, мабуть, стосується будь-якого методу шаблонів на основі Баша.
Алекс Б

Шаблони на базі IMHO Bash - це божевілля, оскільки вам потрібно бути баш програмістом, щоб зрозуміти, що робить ваш шаблон! Але дякую за коментар!
mogsie

@AlexB: Цей підхід буде замінити одинарні лапки, так як вони тільки літерні символи усередині захисної подвійних лапках , а не роздільники рядків , колиeval редактор echo / catкоманд обробляє їх; спробуйте eval "echo \"'\$HOME'\"".
mklement0

21

У мене є таке рішення, як могсі, але з heredoc замість herestring, щоб уникнути уникнення подвійних цитат

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

4
Це рішення підтримує розширення параметра Bash у шаблоні. Мої вибрані необхідні параметри з ${param:?}і вкладеності тексту навколо необов'язкових параметрів. Приклад: ${DELAY:+<delay>$DELAY</delay>}розширюється до нічого, коли DELAY не визначено, а <delay> 17 </delay>, коли DELAY = 17.
Ерік Болінгер

2
Ой! І роздільник EOF може використовувати динамічний рядок, як PID _EOF_$$.
Ерік Болінгер

1
@ mklement0 Принциповим завданням для відстеження нових рядків є використання деякого розширення, наприклад, порожня змінна $trailing_newline чи використання $NL5та переконайтеся, що воно розширюється на 5 нових рядків.
xebeche

@xebeche: Так, розміщення того, що ви пропонуєте, в самому кінці всерединіtemplate.txt , спрацювало б для того, щоб зберегти нові рядки.
mklement0

1
Елегантне рішення, але зауважте, що заміна команди зніме будь-які зворотні рядки з вхідного файлу, хоча це, як правило, не буде проблемою. Ще один крайній випадок: через використання eval, якщоtemplate.txt міститьEOF у власній рядку, він передчасно припиняє дію тут і таким чином порушує команду. (Підказка шапки до @xebeche).
mklement0

18

Редагувати 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

Усі кредити Грегорі Пакошу .


Це видаляє подвійні лапки з вхідних даних і, якщо у вхідному файлі є декілька останніх нових рядків, замінює їх одним.
mklement0

2
Мені знадобилося два менші нахили, щоб вона працювала, тобто eval "echo \"$(sed 's/\"/\\"/g' $1)\""
Девід Бау

На жаль, такий підхід не дозволяє шаблонувати файли php (вони містять $variables).
IStranger

10

Замість того, щоб винаходити колесо, їдьте з envsubst Може використовуватися майже в будь-якому сценарії, наприклад, будувати файли конфігурації зі змінних оточуючих середовищ у контейнерах докера.

Якщо на mac переконайтеся, що у вас є домашня мова, тоді зв’яжіть її з 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

ця послідовність виклику envsubstфактично працює.
Олексій

Якщо хтось, хто шукає, envsubstне працює на MacOS, вам потрібно буде встановити його за допомогою homebrew : brew install gettext.
Метт

9

Більш довга, але надійніша версія прийнятої відповіді:

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

Я зробив би це так, напевно, менш ефективно, але легше читати / підтримувати.

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
Це можна зробити, не читаючи по черзі і лише з одним викликом sed:sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Брендон Блум

8

Якщо ви хочете використовувати шаблони Jinja2 , перегляньте цей проект: j2cli .

Він підтримує:

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

5

Отримавши відповідь від 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

5

Якщо використання Perl - це варіант, і ви задовольняєтесь основою розширень лише на змінних середовища (на відміну від усіх змінних оболонок ), врахуйте надійну відповідь Стюарта П. Бентлі .

Ця відповідь має на меті створити єдине рішення, яке, незважаючи на використання, 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посилань - дивіться цю відповідь мою.


Ось вдосконалена версія єдиного, evalпростого рішення, з прийнятої відповіді :

Поліпшення:

  • Підтримка розширення обох ${name}та $nameзмінних посилань.
  • Підтримка \змінних посилань змінних, які не слід розширювати.
  • На відміну від evalрішення, заснованого вище,
    • неосновні розширення ігноруються
    • неправильно сформовані посилання змінних ігноруються (вони не порушують сценарій)
 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's 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's value using indirect expansion
           lines=${pre}${!varName}${post}
      fi
      end_offset=${#pre}
 done
 printf %s "$lines"

5

Ось ще одне чисте баш-рішення:

  • це використання 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>

4

Ось ще одне рішення: генеруйте 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

Обговорення

  • В дужках відкривається допоміжна оболонка, її мета - згрупувати весь генерований результат
  • В межах під оболонки ми генеруємо всі декларації змінних
  • Також у під оболонці ми генеруємо catкоманду за допомогою HEREDOC
  • Нарешті, ми подаємо вихідний сигнал оболонки на баш і виробляємо потрібний вихід
  • Якщо ви хочете перенаправити цей вихід у файл, замініть останній рядок на:

    ) | bash > output.txt


3

Ідеальний випадок для shtpl . (мій проект, тому він широко не використовується і не вистачає в документації. Але ось рішення, яке він пропонує, як би там не було. Ви можете перевірити його.)

Просто виконайте:

$ i=1 word=dog sh -c "$( shtpl template.txt )"

Результат:

the number is 1
the word is dog

Весело.


1
Якщо це лайно, це все-таки знижується. І я з цим все гаразд. Але гаразд, зауважимо, що це не чітко видно, що це насправді мій проект. Зробимо це зробити більш помітним у майбутньому. Дякуємо як завгодно за ваш коментар та ваш час.
zstegi

Хочу додати, що я вчора дійсно шукав usecases, де shtpl було б ідеальним рішенням. Так, мені було нудно ...
zstegi

3
# 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, що регулюється на ваш смак, використовується у виробництві і не має ламатися на будь-якому вході. Якщо вона зламається - дайте мені знати.



0

Ось функція 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}"
}

0

Ось модифікований perlсценарій на основі кількох інших відповідей:

perl -pe 's/([^\\]|^)\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}/$1.$ENV{$2}/eg' -i template

Особливості (на основі моїх потреб, але їх слід легко змінити):

  • Пропускає розширені параметри розширень (наприклад, \ $ {VAR}).
  • Підтримується розширення параметрів форми $ {VAR}, але не $ VAR.
  • Замінює $ {VAR} порожнім рядком, якщо немає envar VAR.
  • У імені підтримуються лише символи az, AZ, 0-9 та підкреслення (за винятком цифр у першій позиції).


0

Щоб продовжити відповідь на plockc на цій сторінці, ось версія, що підходить для тире, для тих, хто хоче уникати башизмів.

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


-1

Мій скромний внесок у це чудове запитання.

tpl() {
    local file=$(cat - | \
                 sed -e 's/\"/\\"/g' \
                     -e "s/'/\\'/g")
    local vars=$(echo ${@} | tr ' ' ';')
    echo "$(sh -c "${vars};echo \"$file\"")"
}

cat tpl.txt | tpl "one=fish" "two=fish"

Це, по суті, працює, використовуючи допоміжну оболонку для заміни envar, за винятком того, що вона не використовує eval, і явно уникає одинарних та подвійних лапок. Він об'єднує вирази var в один рядок без пробілів, щоб не плутати, shа потім передає шаблон echo, дозволяючи shобробляти замін var. Він зберігає нові рядки, коментарі тощо ... і ви можете втекти, \${like_this}якщо не хочете, щоб вар був інтерпретований. ${missing_var}буде просто замінено порожнім значенням.

Багато інших відповідей тут дуже хороші, але я хотів чогось дуже простого, і для цього не потрібно обробляти буквально кожен можливий випадок шаблонів, які я маю на даний момент.

Насолоджуйтесь!


У вас є кілька серйозних помилок цитування, хоча їх слід легко виправити (див. Коли обертати цитати навколо змінної оболонки? ). Але я бачу тут деякі більш фундаментальні проблеми; деякі складнощі здаються крихкими і зовсім непотрібними.
трійчатка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.