J'écris un script pour automatiser la création de fichiers de configuration pour Apache et PHP pour mon propre serveur Web. Je ne souhaite utiliser aucune interface graphique comme CPanel ou ISPConfig.
J'ai quelques modèles d'Apache et des fichiers de configuration PHP. Le script Bash doit lire les modèles, créer des substitutions de variables et afficher les modèles analysés dans un dossier. Quel est le meilleur moyen de le faire? Je pense Je veux le faire en pur Bash (c’est facile en PHP par exemple)).
1) Comment remplacer $ {} espaces réservés dans un fichier texte?
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, comment puis-je rediriger la sortie vers un fichier externe ici? Dois-je échapper quelque chose si les variables contiennent, par exemple, des citations?
2) Utiliser cat & sed pour remplacer chaque variable par sa valeur:
Étant donné template.txt:
The number is ${i}
The Word is ${Word}
Commander:
cat template.txt | sed -e "s/\${i}/1/" | sed -e "s/\${Word}/dog/"
Cela me semble mauvais en raison de la nécessité d'échapper à beaucoup de symboles différents et avec beaucoup de variables, la ligne sera trop longue.
Pouvez-vous penser à une autre solution élégante et sûre?
Vous pouvez utiliser ceci:
Perl -p -i -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg' < template.txt
pour remplacer toutes les chaînes ${...}
par les variables d'environnement correspondantes (n'oubliez pas de les exporter avant d'exécuter ce script).
Pour pur bash, cela devrait fonctionner (en supposant que les variables ne contiennent pas de chaînes $ {...}):
#!/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
. Solution qui ne se bloque pas si RHS fait référence à une variable qui fait référence à elle-même:
#!/bin/bash line = "$ (cat; echo -na)" end_offset = $ {# line} tant que [["$ {ligne: 0: $ end_offset} "= ~ (. *) (\ $\{([a-zA-Z _] [a-zA-Z_0-9] *) \}) (. *)]; do PREF = "$ {BASH_REMATCH [1]}" POST = "$ {BASH_REMATCH [4]} $ {ligne: $ end_offset: $ {# ligne}}" ".____. ] VARNAME = "$ {BASH_REMATCH [3]}" Eval 'VARVAL = "$' $ VARNAME" "' Line =" $ PRE $ VARVAL $ POST " End_offset = $ {# PRE} Terminé Echo -n "$ {ligne: 0: -1}"
WARNING : Je ne connais pas le moyen de gérer correctement les entrées avec NUL dans bash ou de préserver la quantité de nouvelles lignes. La dernière variante est présentée telle quelle car les coquilles "adorent" les entrées binaires:
read
interprétera les barres obliques inverses.read -r
N'interprétera pas les barres obliques inverses, mais supprimera la dernière ligne si elle ne se termine pas par une nouvelle ligne."$(…)"
supprimera autant de nouvelles lignes qu'il y a de lignes, donc je termine …
par ; echo -n a
et utilise echo -n "${line:0:-1}"
: ceci supprime le dernier caractère (qui est a
) et conserve autant de nouvelles lignes qu'il y en avait dans l'entrée (y compris no).Essayez envsubst
FOO=foo
BAR=bar
export FOO BAR
envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF
envsubst était nouveau pour moi. Fantastique.
Pour mémoire, utiliser un heredoc est un excellent moyen de modéliser un fichier de configuration.
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
Je suis d’accord avec l’utilisation de sed: c’est le meilleur outil de recherche/remplacement. Voici mon approche:
$ 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
Je pense que eval fonctionne vraiment bien. Il gère les modèles avec des sauts de ligne, des espaces et toutes sortes de choses bash. Si vous avez un contrôle total sur les modèles eux-mêmes, bien sûr:
$ 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"
Bien sûr, cette méthode doit être utilisée avec précaution, car eval peut exécuter du code arbitraire. Exécuter ceci en tant que root est à peu près hors de question. Les citations du modèle doivent être échappées, sinon elles seront consommées par eval
.
Vous pouvez également utiliser ici des documents si vous préférez cat
à echo
$ eval "cat <<< \"$(<template.txt)\"" 2> /dev/null
@plockc a fourni une solution qui évite le problème d'échappement des guillemets bash:
$ eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
Edit: Partie supprimée sur son exécution en tant que root avec Sudo ...
Edit: Ajout d'un commentaire sur la façon dont les guillemets doivent être évités, ajout de la solution de plockc au mix!
J'ai une solution bash comme mogsie mais avec heredoc au lieu de herestring pour vous permettre d'éviter d'échapper aux guillemets
eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null
J'avais besoin de garder les guillemets doubles dans mon fichier de configuration afin de pouvoir éviter les guillemets doubles avec sed:
render_template() {
eval "echo \"$(sed 's/\"/\\\\"/g' $1)\""
}
Je ne peux pas penser à garder de nouvelles lignes, mais des lignes vides entre les deux sont conservées.
Bien qu'il s'agisse d'un sujet ancien, l'OMI a trouvé ici une solution plus élégante: 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
Tous les crédits à Grégory Pakosz .
Une version plus longue mais plus robuste de la réponse acceptée:
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
Cela étend toutes les instances de $VAR
ou ${VAR}
à leurs valeurs d’environnement (ou, s’ils ne sont pas définis, à la chaîne vide).
Il échappe correctement aux barres obliques inverses et accepte un $ avec barre oblique inversée pour empêcher la substitution (contrairement à envsubst, qui, il s'avère, ne le fait pas ).
Donc, si votre environnement est:
FOO=bar
BAZ=kenny
TARGET=backslashes
NOPE=engi
et votre modèle est:
Two ${TARGET} walk into a \\$FOO. \\\\
\\\$FOO says, "Delete C:\\Windows\\System32, it's a virus."
$BAZ replies, "\${NOPE}s."
le résultat serait:
Two backslashes walk into a \bar. \\
\$FOO says, "Delete C:\Windows\System32, it's a virus."
kenny replies, "${NOPE}s."
Si vous souhaitez uniquement échapper les barres obliques inverses avant $ (vous pouvez écrire "C:\Windows\System32" dans un modèle inchangé), utilisez cette version légèrement modifiée:
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
Je l'aurais fait de cette façon, probablement moins efficace, mais plus facile à lire/à maintenir.
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
Au lieu de réinventer la roue, allez avec envsubst Peut être utilisé dans presque tous les scénarios, par exemple la construction de fichiers de configuration à partir de variables d'environnement conteneurs docker.
Si sur mac, assurez-vous que vous avez homebrew alors reliez-le à partir de 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
Maintenant, utilisez-le:
# 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
En prenant la réponse de ZyX en utilisant bash pur mais avec un nouveau style de correspondance de regex et de substitution de paramètre indirect, cela devient:
#!/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
Voici une autre solution pure bash:
$ cat code
#!/bin/bash
LISTING=$( ls )
cat_template() {
echo "cat << EOT"
cat "$1"
echo EOT
}
cat_template template | LISTING="$LISTING" bash
$ cat template
(avec des retours à la ligne et des guillemets doubles)
<html>
<head>
</head>
<body>
<p>"directory listing"
<pre>
$( echo "$LISTING" | sed 's/^/ /' )
<pre>
</p>
</body>
</html>
sortie
<html>
<head>
</head>
<body>
<p>"directory listing"
<pre>
code
template
<pre>
</p>
</body>
</html>
Si vous utilisez , Perl est une option et vous vous contentez de fonder les extensions sur environnement variables seulement (par opposition à toutes Shell variables), considérez le robuste de Stuart P. Bentley réponse.
Cette réponse vise à fournir une solution uniquement en bash qui, malgré l'utilisation de eval
- devrait être sûr à utiliser .
Les objectifs sont les suivants:
${name}
Et $name
.$(...)
et syntaxe héritée `...`
)$((...))
et syntaxe héritée $[...]
).\
(\${name}
)."
et \
. Fonction 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' '`(['
}
Exemples:
$ 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
${HOME:0:10}
, Dans la mesure où elles ne contiennent aucune substitution incorporée de commande ou arithmétique, telle que ${HOME:0:$(echo 10)}
$(
Et `
Sont échappées à l'aveuglette).${HOME
(Fermeture manquante }
) Interrompent la fonction.\$name
Empêche l'expansion.\
Non suivi de $
Est conservé tel quel.\
, Vous devez les doubler; par exemple.:\\
-> \
- identique à \
\\\\
-> \\
0x1
, 0x2
, 0x3
.eval
.Si vous recherchez une solution plus restrictive que seulement prend en charge les extensions ${name}
- c'est-à-dire, avec obligatoire accolades, ignorant les références $name
- voir cette réponse à moi.
Voici une version améliorée de de la solution sans base de bash, eval
- de la réponse acceptée:
Les améliorations sont:
${name}
Et $name
.\
- échappant aux références de variables qui ne devraient pas être développées.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"
Voici une autre solution: générer un script bash avec toutes les variables et le contenu du fichier de modèle, ce script ressemblerait à ceci:
Word=dog
i=1
cat << EOF
the number is ${i}
the Word is ${Word}
EOF
Si nous alimentons ce script dans bash, il produira le résultat souhaité:
the number is 1
the Word is dog
Voici comment générer ce script et le nourrir dans bash:
(
# Variables
echo Word=dog
echo i=1
# add the template
echo "cat << EOF"
cat template.txt
echo EOF
) | bash
cat
avec HEREDOC.Si vous souhaitez rediriger cette sortie dans un fichier, remplacez la dernière ligne par:
) | bash > output.txt
Cette page décrit un réponse avec awk
awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt
Cas parfait pour shtpl . (mon projet, il est donc peu utilisé et manque de documentation. Mais voici la solution qu’il offre de toute façon. Puissiez-vous le tester?)
Il suffit d'exécuter:
$ i=1 Word=dog sh -c "$( shtpl template.txt )"
Le résultat est:
the number is 1
the Word is dog
S'amuser.
# 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"
}
C'est la fonction pure bash ajustable à votre goût, utilisée en production et qui ne devrait casser aucune entrée. Si ça casse - faites le moi savoir.
Vous pouvez également utiliser bashible (qui utilise en interne la méthode d'évaluation décrite ci-dessus/ci-dessous).
Il y a un exemple, comment générer un HTML à partir de plusieurs parties:
https://github.com/mig1984/bashible/tree/master/examples/templates
Voici un script modifié Perl
basé sur quelques-unes des autres réponses:
Perl -pe 's/([^\\]|^)\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}/$1.$ENV{$2}/eg' -i template
Caractéristiques (basées sur mes besoins, mais devraient être faciles à modifier):
Examinez la substitution de variables simples python ici: https://github.com/jeckep/vsubst
C'est très simple à utiliser:
python subst.py --props secure.properties --src_path ./templates --dst_path ./dist
Voici une fonction bash qui préserve les espaces:
# 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}"
}