web-dev-qa-db-ja.com

Bashテンプレート:Bashを使用してテンプレートから構成ファイルを作成する方法

ApacheとPHPの構成ファイルの作成を自動化するスクリプトを自分のWebサーバー用に書いています。 CPanelやISPConfigなどのGUIは使用したくありません。

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"

ところで、ここで外部ファイルに出力をリダイレクトするにはどうすればよいですか?変数に引用符が含まれている場合、何かをエスケープする必要がありますか?

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

多くの異なるシンボルをエスケープする必要があるため、私には悪いようであり、多くの変数を使用すると、行が長くなります。

他のエレガントで安全なソリューションを考えていただけますか?

109

これを使用できます:

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 -na)" 
 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}" 

WARNING:bashのNULを使用して入力を正しく処理する方法や、末尾の改行の量を保持する方法がわかりません。最後のバリアントは、シェルがバイナリ入力を「愛する」ため、そのまま表示されます。

  1. readはバックスラッシュを解釈します。
  2. read -rはバックスラッシュを解釈しませんが、改行で終わらない場合は最後の行を削除します。
  3. "$(…)"は、存在する限り多くの末尾の改行を削除するので、; echo -n aで終了し、echo -n "${line:0:-1}"を使用します。これにより、最後の文字(a)そして、入力にあったのと同じ数の末尾の改行を保持します(noを含む)。
57
ZyX

envsubstを試してください

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF
111
yottatsa

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
36
Dan Garthwaite

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
31
Hai Vu

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 quoteのエスケープの問題を回避するソリューションを提案しました。

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

編集: Sudoを使用してこれをrootとして実行することに関する部分を削除しました...

編集:引用符をエスケープする方法についてのコメントを追加し、plockcのソリューションをミックスに追加しました!

21
mogsie

Mogsieのようなbashソリューションがありますが、二重引用符のエスケープを回避できるように、herestringの代わりにheredocを使用します

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

2017年1月6日編集

構成ファイルに二重引用符を保持する必要があったため、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égoryPakosz へのすべてのクレジット。

16
CKK

受け入れられた答えのより長いがより堅牢なバージョン:

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のすべてのインスタンスが展開されますor${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
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
8
Craig552uk

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
8
smentek

Jinja2 テンプレートを使用する場合は、このプロジェクトを参照してください: j2cli

以下をサポートします。

  • JSON、INI、YAMLファイルおよび入力ストリームからのテンプレート
  • 環境変数からのテンプレート化
8
kolypto

純粋なbashを使用してZyXから答えを取得しますが、新しいスタイルの正規表現マッチングと間接的なパラメーター置換を使用すると、次のようになります。

#!/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
wich

別の純粋なbashソリューションを次に示します。

  • ヒアドキュメントを使用しているため、
    • 追加の構文が必要なため、複雑さは増しません
    • テンプレートには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

Perlを使用するオプションがあり、environment変数のみ(allShell変数)、検討 スチュアートP.ベントレーの堅牢な答え.

この答えは、bash-only solutionを提供することを目指しています-evalの使用にもかかわらず-safe to useであるべきです。

goalsは次のとおりです。

  • ${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:$(echo 10)} などの埋め込みコマンドまたは算術置換が含まれていない限り、${HOME:0:10}などのnon-basic変数展開もサポートします。
    • このような埋め込み置換は、実際に機能を中断します(すべての$(および`インスタンスが盲目的にエスケープされるため)。
    • 同様に、${HOME(閉じる}の欠落)などの不正な形式の変数参照により、関数が破損します。
  • Bashは二重引用符で囲まれた文字列を処理するため、バックスラッシュは次のように処理されます。
    • \$nameは展開を防ぎます。
    • \が後に続かない単一の$はそのまま保持されます。
    • 複数の隣接する\インスタンスを表現する場合は、それらをdoubleする必要があります。例えば。:
      • \\-> \-\と同じ
      • \\\\-> \\
    • 入力には、内部目的で使用される次の(まれに使用される)文字を含めることはできません:0x10x20x3
  • Bashが新しい展開構文を導入する場合、この関数がそのような展開を妨げない可能性があるという大部分が仮説的な懸念があります-evalを使用しないソリューションについては以下を参照してください。

only${name}拡張をサポートする、より制限的なソリューションを探している場合-つまり、mandatory$name参照を無視する中括弧- この答え を参照.


bash-onlyの改良バージョン、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"
4
mklement0

別のソリューションを次に示します。すべての変数とテンプレートファイルの内容を含む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

討論

  • 括弧はサブシェルを開きます。その目的は、生成されたすべての出力をグループ化することです
  • サブシェル内で、すべての変数宣言を生成します
  • また、サブシェルでは、HEREDOCを使用してcatコマンドを生成します
  • 最後に、サブシェルの出力をbashに送り、目的の出力を生成します
  • この出力をファイルにリダイレクトする場合は、最後の行を次のように置き換えます。

    ) | bash > output.txt
    
3
Hai Vu

このページでは、 awkでの回答 について説明します

awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt
3
Matt Brown

shtpl の場合に最適です。 (私のプロジェクトですので、あまり使用されておらず、ドキュメントもありません。しかし、とにかく、それが提供するソリューションがあります。テストしてください。)

実行するだけです:

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

結果は次のとおりです。

the number is 1
the Word is dog

楽しんで。

3
zstegi
# 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関数であり、本番環境で使用され、どの入力でも壊れないはずです。それが壊れた場合-私に知らせてください。

2
ttt

また、bashibleを使用することもできます(内部的には上記または下記の評価アプローチを使用します)。

複数の部分からHTMLを生成する方法の例があります。

https://github.com/mig1984/bashible/tree/master/examples/templates

1
Jan Molič

他のいくつかの回答に基づいて変更されたPerlスクリプトは次のとおりです。

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

機能(私のニーズに基づいていますが、簡単に変更できるはずです):

  • エスケープされたパラメーター展開(たとえば、\ $ {VAR})をスキップします。
  • $ VARではなく、$ {VAR}形式のパラメーター展開をサポートします。
  • VAR envarがない場合、$ {VAR}を空白文字列に置き換えます。
  • 名前のa〜z、A〜Z、0〜9、およびアンダースコアのみをサポートします(最初の位置の数字を除く)。
0
Kevin

ここで単純な変数置換pythonスクリプトを見てください: https://github.com/jeckep/vsubst

使い方はとても簡単です:

python subst.py --props secure.properties --src_path ./templates --dst_path ./dist
0
jeckep

空白を保持する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
Igor Katson