diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3c14744 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +language: bash +script: ./test.sh diff --git a/README.md b/README.md index 89b5e82..70ba73d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # BASH Templater -Very simple templating system that replace {{VAR}} by $VAR environment value -Supports default values by writting {{VAR=value}} in the template + +Very simple templating system that replace `{{VAR}}` by `$VAR` environment value. + +Supports default values by writting `{{VAR=value}}` in the template. + +[![Build Status](https://travis-ci.org/lavoiesl/bash-templater.svg?branch=master)](https://travis-ci.org/lavoiesl/bash-templater) ## Author @@ -12,36 +16,38 @@ See http://code.haleby.se/2015/11/20/simple-templating-engine-in-bash/ and http ## Installation -To install templater in linux type: +`templater.sh` has no external dependencies. You can use it by directly executing. + +To install `templater.sh` globally in Linux, type: - sudo curl -L https://raw.githubusercontent.com/johanhaleby/bash-templater/master/templater.sh -o /usr/local/bin/templater - sudo chmod +x /usr/local/bin/templater + sudo curl -L https://raw.githubusercontent.com/johanhaleby/bash-templater/master/templater.sh -o /usr/local/bin/templater.sh + sudo chmod +x /usr/local/bin/templater.sh ## Usage -```bash -VAR=value templater template +VAR=value templater.sh template ``` Read variables from file: ```bash -templater template -f variables.txt +templater.sh template -f variables.txt ``` -e.g.: ```bash -# variables.txt -# The author -AUTHOR=Johan -# The version -VERSION=1.2.3 -``` +# Using external configuration file (and don't print the warnings) +templater.sh template -f variables.txt -s -Don't print any warning messages: +# Passing arguments directly +VAR=value templater.sh template -```bash -templater template -f variables.txt -s +# Evaluate /tmp/foo and pass those variables to the template +# Useful for defining variables in a file +# Parentheses are important for not polluting the current shell +(set -a && . /tmp/foo && templater.sh template) + +# A variant that does NOT pass current env variables to the templater +sh -c "set -a && . /tmp/foo && templater.sh template" ``` ## Examples diff --git a/examples/vhost-php.conf b/examples/vhost-php.conf index 682dacf..e7767a0 100644 --- a/examples/vhost-php.conf +++ b/examples/vhost-php.conf @@ -1,17 +1,11 @@ -{{LOG_DIR=/var/log/apache2}} -{{RUN_DIR=/var/run/php-fpm}} -{{FCGI=$RUN_DIR/$DOMAIN.fcgi}} -{{SOCKET=$RUN_DIR/$DOMAIN.sock}} -{{EMAIL=$USER@$DOMAIN}} -{{DOC_ROOT=/home/$USER/sites/$DOMAIN/htdocs}} - ServerAdmin {{EMAIL}} - ServerName {{DOMAIN}} - ServerAlias www.{{DOMAIN}} + ServerAdmin nobody@example.com + ServerName example.com + ServerAlias www.example.com - DocumentRoot "{{DOC_ROOT}}" + DocumentRoot "/home/nobody/sites/example.com/htdocs" - + AllowOverride All Order allow,deny Allow From All @@ -19,10 +13,10 @@ AddHandler php-script .php Action php-script /php5.fastcgi virtual - Alias /php5.fastcgi {{FCGI}} - FastCGIExternalServer {{FCGI}} -socket {{SOCKET}} + Alias /php5.fastcgi /var/run/php-fpm/example.com.fcgi + FastCGIExternalServer /var/run/php-fpm/example.com.fcgi -socket /var/run/php-fpm/example.com.sock LogLevel warn - CustomLog {{LOG_DIR}}/{{DOMAIN}}.access.log combined - ErrorLog {{LOG_DIR}}/{{DOMAIN}}.error.log + CustomLog /var/log/apache2/example.com.access.log combined + ErrorLog /var/log/apache2/example.com.error.log diff --git a/examples/vhost-php.tpl.conf b/examples/vhost-php.tpl.conf new file mode 100644 index 0000000..2e773f7 --- /dev/null +++ b/examples/vhost-php.tpl.conf @@ -0,0 +1,28 @@ +{{LOG_DIR=/var/log/apache2}} +{{RUN_DIR=/var/run/php-fpm}} +{{FCGI=$RUN_DIR/$DOMAIN.fcgi}} +{{SOCKET=$RUN_DIR/$DOMAIN.sock}} +{{EMAIL=$USER@$DOMAIN}} +{{DOC_ROOT=/home/$USER/sites/$DOMAIN/htdocs}} + + ServerAdmin {{EMAIL}} + ServerName {{DOMAIN}} + ServerAlias www.{{ DOMAIN }} + + DocumentRoot "{{DOC_ROOT}}" + + + AllowOverride All + Order allow,deny + Allow From All + + + AddHandler php-script .php + Action php-script /php5.fastcgi virtual + Alias /php5.fastcgi {{FCGI}} + FastCGIExternalServer {{ FCGI }} -socket {{SOCKET}} + + LogLevel warn + CustomLog {{LOG_DIR}}/{{DOMAIN}}.access.log combined + ErrorLog {{LOG_DIR }}/{{ DOMAIN}}.error.log + diff --git a/templater.sh b/templater.sh index e96f2ff..af11b00 100755 --- a/templater.sh +++ b/templater.sh @@ -34,6 +34,8 @@ readonly PROGNAME=$(basename $0) config_file="" print_only="false" silent="false" +nounset="false" +verbose="false" usage="${PROGNAME} [-h] [-d] [-f] [-s] -- @@ -46,6 +48,10 @@ where: Specify a file to read variables from -s, --silent Don't print warning messages (for example if no variables are found) + -u, --nounset + Unset variables throws error instead of a warning + -v, --verbose + Verbose output examples: VAR1=Something VAR2=1.2.3 ${PROGNAME} test.txt @@ -57,13 +63,6 @@ if [ $# -eq 0 ]; then exit 1 fi -if [[ ! -f "${1}" ]]; then - echo "You need to specify a template file" >&2 - echo "$usage" - exit 1 -fi - -template="${1}" if [ "$#" -ne 0 ]; then while [ "$#" -gt 0 ] @@ -76,32 +75,47 @@ if [ "$#" -ne 0 ]; then -p|--print) print_only="true" ;; - -f|--file) - config_file="$2" + -f|--file) shift + config_file="$1" ;; -s|--silent) silent="true" ;; - --) - break + -u|--nounset) + nounset="true" + ;; + -v|--verbose) + verbose="true" ;; -*) echo "Invalid option '$1'. Use --help to see the valid options" >&2 exit 1 ;; - # an option argument, continue - *) ;; + # an option argument, this must be the template + *) + template="$1" + ;; esac shift done fi -vars=$(grep -oE '\{\{[A-Za-z0-9_]+\}\}' "${template}" | sort | uniq | sed -e 's/^{{//' -e 's/}}$//') +if [[ ! -f "$template" ]]; then + echo "You need to specify a template file" >&2 + echo "$usage" + exit 1 +fi + + + +vars=$(grep -oE '\{\{\s*[A-Za-z0-9_]+\s*\}\}' "$template" | sort | uniq | sed -e 's/^{{//' -e 's/}}$//') if [[ -z "$vars" ]]; then - if [ "$silent" == "false" ]; then - echo "Warning: No variable was found in ${template}, syntax is {{VAR}}" >&2 + if [ "$verbose" == "true" ]; then + echo "Warning: No variable was found in ${template}" >&2 fi + cat $template + exit 0 fi # Load variables from file if needed @@ -111,42 +125,72 @@ if [ "${config_file}" != "" ]; then echo "$usage" exit 1 fi - + _pwd=$PWD + cd "$(dirname "$config_file")" source "${config_file}" + cd "$_pwd" fi var_value() { - eval echo \$$1 + var="${1}" + eval echo \$"${var}" +} + +## +# Escape custom characters in a string +# Example: escape "ab'\c" '\' "'" ===> ab\'\\c +# +function escape_chars() { + local content="${1}" + shift + + for char in "$@"; do + content="${content//${char}/\\${char}}" + done + + echo "${content}" +} + +function echo_var() { + local var="${1}" + local content="${2}" + local escaped="$(escape_chars "${content}" "\\" '"')" + + echo "${var}=\"${escaped}\"" } -replaces="" +declare -a replaces +replaces=() # Reads default values defined as {{VAR=value}} and delete those lines # There are evaluated, so you can do {{PATH=$HOME}} or {{PATH=`pwd`}} # You can even reference variables defined in the template before defaults=$(grep -oE '^\{\{[A-Za-z0-9_]+=.+\}\}' "${template}" | sed -e 's/^{{//' -e 's/}}$//') - +#????defaults=$(grep -oE '^\{\{[A-Za-z0-9_]+=.+\}\}$' "${template}" | sed -e 's/^{{//' -e 's/}}$//') +IFS=$'\n' for default in $defaults; do - var=$(echo "$default" | grep -oE "^[A-Za-z0-9_]+") - current=`var_value $var` + var=$(echo "${default}" | grep -oE "^[A-Za-z0-9_]+") + current="$(var_value "${var}")" # Replace only if var is not set - if [[ -z "$current" ]]; then - eval $default + if [[ -n "$current" ]]; then + eval "$(echo_var "${var}" "${current}")" + else + eval "${default}" fi # remove define line - replaces="-e '/^{{$var=/d' $replaces" - vars="$vars -$current" + replaces+=("-e") + replaces+=("/^{{${var}=/d") + vars="${vars} ${var}" done -vars=$(echo $vars | sort | uniq) +vars="$(echo "${vars}" | tr " " "\n" | sort | uniq)" if [[ "$print_only" == "true" ]]; then for var in $vars; do - value=`var_value $var` - echo "$var = $value" + value="$(var_value "${var}")" + echo_var "${var}" "${value}" done exit 0 fi @@ -155,15 +199,18 @@ fi for var in $vars; do value=$(var_value $var | sed -e "s;\&;\\\&;g" -e "s;\ ;\\\ ;g") # '&' and is escaped if [[ -z "$value" ]]; then - if [ $silent == "false" ]; then - echo "Warning: $var is not defined and no default is set, replacing by empty" >&2 + if [[ $nounset == "false" ]]; then + [ $silent == "true" ] || echo "Warning: $var is not defined and no default is set, replacing by empty" >&2 + else + echo "ERROR: $var is not defined and no default is set." >&2 + exit 1 fi fi # Escape slashes - value=$(echo "$value" | sed 's/\//\\\//g'); - replaces="-e 's/{{$var}}/${value}/g' $replaces" + value="$(escape_chars "${value}" "\\" '/' ' ')"; + replaces+=("-e") + replaces+=("s/{{\s*${var}\s*}}/${value}/g") done -escaped_template_path=$(echo $template | sed 's/ /\\ /g') -eval sed $replaces "$escaped_template_path" +sed "${replaces[@]}" "${template}" diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..56af89b --- /dev/null +++ b/test.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +if diff -u <(USER=nobody DOMAIN=example.com ./templater.sh examples/vhost-php.tpl.conf) examples/vhost-php.conf; then + echo OK +else + echo Differences found + exit 1 +fi