159

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

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

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

他のエレガントで安全なソリューションを考えられますか?

4

23 に答える 23

192

試すenvsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF
于 2012-06-15T12:48:40.780 に答える
67
于 2010-05-26T19:35:58.720 に答える
67

envsubst は私にとって初めてでした。素晴らしい。

記録として、ヒアドキュメントを使用することは、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
于 2012-08-17T13:33:14.693 に答える
40

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
于 2010-05-26T18:13:52.723 に答える
25

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 は任意のコードを実行できるため、このメソッドは注意して使用する必要があります。これをルートとして実行することは、ほとんど問題外です。テンプレート内の引用符はエスケープする必要があります。そうしないと、 によって食べられてしまいますeval

cat必要に応じて、こちらのドキュメントを使用することもできますecho

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

@plockc は、bash の引用符のエスケープの問題を回避する解決策を提案しました。

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

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

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

于 2012-09-14T10:08:08.803 に答える
18

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égory Pakoszのすべての功績。

于 2014-02-02T18:45:55.880 に答える
11

おそらく効率は劣りますが、読みやすく、保守しやすい方法です。

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
于 2010-05-28T11:07:05.333 に答える
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 とは異なり、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
于 2014-07-29T15:28:49.140 に答える
5

Perlの使用がオプションであり、 (すべてのシェル変数ではなく)環境変数のみに基づいて展開することに満足している場合は、Stuart P. Bentley の堅牢な回答を検討してください。

この回答は、使用しても安全に使用できる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)}
    • このような埋め込み置換は、実際には関数を壊します (すべて$(`インスタンスがやみくもにエスケープされるため)。
    • ${HOME同様に、 (missing Closing }) BREAK 関数などの不正な変数参照。
  • 二重引用符で囲まれた文字列の bash の処理により、バックスラッシュは次のように処理されます。
    • \$name拡大を防ぎます。
    • \後に続かないシングル$はそのまま保持されます。
    • 複数の隣接する \インスタンスを表す場合は、それらを 2 倍にする必要があります。例えば:
      • \\-> \- ただと同じ\
      • \\\\->\\
    • 入力には、内部目的で使用される次の (めったに使用されない) 文字を含めてはなりません: 0x10x20x3.
  • bash が新しい展開構文を導入する必要がある場合、この関数はそのような展開を妨げない可能性があるという、主に仮説上の懸念があります - を使用しない解決策については、以下を参照してくださいeval

拡張のみをサポートするより制限的なソリューションを${name}探している場合、つまり、必須の中括弧を使用して、$name参照を無視する場合は、 this answer of mineを参照してください。


これは、受け入れられた回答からのbashのみの無料のソリューションの改良版です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"
于 2015-03-25T23:32:52.490 に答える
4

shtplの完璧なケース。(私のプロジェクトなので、広く使用されておらず、ドキュメントも不足しています。しかし、とにかく提供するソリューションは次のとおりです。テストしてみてください。)

実行するだけです:

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

結果は次のとおりです。

the number is 1
the word is dog

楽しむ。

于 2013-03-03T04:42:19.303 に答える
3

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

awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt
于 2012-03-06T19:39:52.680 に答える
1

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

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

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

于 2015-09-03T13:30:47.750 に答える