##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 ## Gestion des fichiers de configuration ##@cooked nocomments ##@require base uprovide conf urequire base ################################################################################ # Gestion des fichiers de configuration où les directives sont de la forme # 'name=value' et où les commentaires débutent par '#'. C'est typiquement le cas # des scripts de profile du shell # Note: #@$*! de MacOS X qui ne supporte pas \(expr\)\? dans sed. Toutes les # lignes sed de la forme '/prefix\(expr\)\?suffix/s/from/to/g' sont remplacées # par une double expression # '/prefixexprsuffix/s/from/to/g # /prefixsuffix/s/from/to/g' function conf_enable() { # Dans le fichier de configuration $1, activer les paramètres $2..* # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name[=value] # Dans tous les cas, toutes les directives de ce nom sont recherchées et # décommentées. Si value est précisée, les directives sont mises à jour. Si # la directive ne figure pas dans le fichier, elle y est rajoutée à la fin # avec la valeur spécifiée. # Retourner 0 si une modification a été faite dans le fichier, 1 sinon local conf="$1"; shift [ -n "$*" ] || return 1 local mode="$(fix_mode "$conf")" local modified=1 local param name value to for param in "$@"; do splitvar "$param" name value # Essayer d'abord de décommenter la valeur dans le fichier if quietgrep "^[ $TAB]*##*[ $TAB]*\(export[ $TAB]*\)\?$name[ $TAB]*=" "$conf"; then sedi "\ /^[ $TAB]*##*[ $TAB]*export[ $TAB]*$name[ $TAB]*=/s/^[ $TAB]*##*\([ $TAB]*export[ $TAB]*\)/\\1/g /^[ $TAB]*##*[ $TAB]*$name[ $TAB]*=/s/^[ $TAB]*##*//g " "$conf" modified=0 elif ! quietgrep "^[ $TAB]*\(export[ $TAB]*\)\?$name[ $TAB]*=" "$conf"; then echo "$name=" >>"$conf" modified=0 fi if [ "$name" != "$param" ]; then # Ensuite, mettre à jour le fichier avec la valeur spécifiée to="$(quote_seds "$value")" sedi "\ /^[ $TAB]*export[ $TAB]*$name[ $TAB]*=/s/^\([ $TAB]*export[ $TAB]*$name[ $TAB]*=[ $TAB]*\).*\$/\\1$to/ /^[ $TAB]*$name[ $TAB]*=/s/^\([ $TAB]*$name[ $TAB]*=[ $TAB]*\).*\$/\\1$to/ " "$conf" modified=0 fi done unfix_mode "$conf" "$mode" return $modified } function conf_enableq() { # Comme conf_enable(), mais s'assure que les valeurs sont quotées dans le # fichier. Ceci permet de stocker des valeurs avec des espaces ou des # caractères spéciaux. local args arg name value args=("$1"); shift for arg in "$@"; do splitvar "$arg" name value array_add args "$name=$(quoted_arg "$value")" done conf_enable "${args[@]}" } function conf_disable() { # Dans le fichier de configuration $1, désactiver les paramètres $2..* # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name[=value] # Toutes les directives de ce noms sont recherchées et commentées. La valeur # si elle est spécifiée, est ignorée. Si la directive ne figure pas dans le # fichier, c'est un NOP. # Retourner 0 si une modification a été faite dans le fichier, 1 sinon local conf="$1"; shift [ -n "$*" ] || return 1 local mode="$(fix_mode "$conf")" local modified=1 local param name value from0 from1 for param in "$@"; do splitvar "$param" name value # Essayer simplement de commenter la directive dans le fichier from0="^[ $TAB]*export[ $TAB]*$name[ $TAB]*=" from1="^[ $TAB]*$name[ $TAB]*=" if [ "$name" != "$param" ]; then from0="$from0$(quote_seds "$value")\$" from1="$from1$(quote_seds "$value")\$" fi if quietgrep "$from" "$conf"; then sedi "\ /$from0/s/^/#/g /$from1/s/^/#/g " "$conf" modified=0 fi done unfix_mode "$conf" "$mode" return $modified } CONF_APPEND_SEP=: function conf_append() { # Dans le fichier de configuration $1, augmenter les valeurs des variables # correspondant aux paramètres $2..* # Chaque argument de cette fonction correspond à une variable du fichier de # configuration, et doit être de la forme name=value # Une ligne 'name="${name:+$name:}$value"' est générée à la fin du fichier # de configuration. # Par défaut, le séparateur CONF_APPEND_SEP vaut ':', mais il est possible # de changer cette valeur, de façon globale # Retourner 0 si une modification a été faite dans le fichier, 1 sinon local conf="$1"; shift [ -n "$*" ] || return 1 local mode="$(fix_mode "$conf")" local modified=1 local param name value from for param in "$@"; do splitvar "$param" name value echo "$name=\"\${$name:+\$$name$CONF_APPEND_SEP}$(quote_arg "$value")\"" >>"$conf" modified=0 done unfix_mode "$conf" "$mode" return $modified } function conf_array_append() { # Dans le fichier de configuration $1, augmenter les valeurs des variables # de tableau correspondant aux paramètres $2..* # Chaque argument de cette fonction correspond à une variable du fichier de # configuration, et doit être de la forme name=value # Une ligne name=("${name[@]}" "$value") est générée à la fin du fichier de # configuration # Retourner 0 si une modification a été faite dans le fichier, 1 sinon local conf="$1"; shift [ -n "$*" ] || return 1 local mode="$(fix_mode "$conf")" local modified=1 local param name value from for param in "$@"; do splitvar "$param" name value if quietgrep "^[ $TAB]*\(export[ $TAB]*\)\?$name=(" "$conf"; then # variable déjà existante [ "$name" != "$param" ] || continue echo "$name=(\"\${$name[@]}\" $(quoted_arg "$value"))" >>"$conf" else # nouvelle variable if [ "$name" != "$param" ]; then echo "$name=($(quoted_arg "$value"))" >>"$conf" else echo "$name=()" >>"$conf" fi fi modified=0 done unfix_mode "$conf" "$mode" return $modified } function conf_check() { # Dans le fichier de configuration $1, tester si tous les paramètres $2..* # sont présents. # Chaque argument de cette fonction correspond à une variable du fichier de # configuration, et doit être de la forme name[=value] # Si une valeur est spécifiée, vérifier que le fichier contient la valeur # correspondante. Sinon, tester uniquement la présence de la directive. local conf="$1"; shift [ -n "$*" ] || return 1 local param name value from for param in "$@"; do splitvar "$param" name value from="^[ $TAB]*\(export[ $TAB]*\)\?$name[ $TAB]*=" if [ "$name" != "$param" ]; then from="$from$(quote_seds "$value")\$" fi quietgrep "$from" "$conf" || return 1 done return 0 } ################################################################################ # Gestion des fichiers de configuration où les directives sont de la forme 'name # value', et où les commentaires débutent par '#'. C'est typiquement le cas des # fichiers de configuration d'apache function aconf_enable() { # Dans le fichier de configuration $1, activer les paramètres $2..* # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name[=value] # Toutes les directives de ce nom sont recherchées et décommentées, et la # valeur mise à jour. Si la directive ne figure pas dans le fichier, elle y # est rajoutée à la fin. A cause du mode opératoire, cette fonction ne # convient pas pour les directives dont le nom peut apparaitre plusieurs # fois dans le fichier # Retourner 0 si une modification a été faite dans le fichier, 1 sinon local conf="$1"; shift [ -n "$*" ] || return 1 local mode="$(fix_mode "$conf")" local modified=1 local param name value to for param in "$@"; do splitvar "$param" name value # Essayer d'abord de décommenter la valeur dans le fichier if quietgrep "^[ $TAB]*##*[ $TAB]*$name[ $TAB]*" "$conf"; then sedi "/^[ $TAB]*##*[ $TAB]*$name[ $TAB]*/s/^[ $TAB]*##*\([ $TAB]*\)/\\1/g" "$conf" modified=0 elif ! quietgrep "^[ $TAB]*$name[ $TAB]*" "$conf"; then echo "$name" >>"$conf" modified=0 fi if [ "$name" != "$param" ]; then # Ensuite, mettre à jour le fichier avec la valeur spécifiée to="$(quote_seds "$value")" sedi "\ /^[ $TAB]*$name[ $TAB][ $TAB]*/s/^\([ $TAB]*$name[ $TAB]*\).*$/\\1$to/ /^[ $TAB]*$name\$/s/^\([ $TAB]*$name\)\$/\\1 $to/" "$conf" modified=0 fi done unfix_mode "$conf" "$mode" return $modified } function aconf_disable() { # Dans le fichier de configuration $1, désactiver les paramètres $2..* # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name[=value] # Si la valeur est précisée, la directive correspondant à ce nom et cette # valeur est recherchée et commentée. Sinon, toutes les directives de ce # noms sont recherchées et commentées. Si la directive ne figure pas dans le # fichier, c'est un NOP. # Retourner 0 si une modification a été faite dans le fichier, 1 sinon local conf="$1"; shift [ -n "$*" ] || return 1 local mode="$(fix_mode "$conf")" local modified=1 local param name value from for param in "$@"; do splitvar "$param" name value # Essayer simplement de commenter la valeur dans le fichier from="^[ $TAB]*$name[ $TAB]*" if [ "$name" != "$param" ]; then from="$from$(quote_seds "$value")\$" fi if quietgrep "$from" "$conf"; then sedi "/$from/"'s/^/#/g' "$conf" modified=0 fi done unfix_mode "$conf" "$mode" return $modified } function aconf_append() { # Dans le fichier de configuration $1, ajouter des directives correspondant # aux paramètres $2..* # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name=value # Une ligne '$name $value' est ajoutée à la fin du fichier de configuration # Retourner 0 si une modification a été faite dans le fichier, 1 sinon local conf="$1"; shift [ -n "$*" ] || return 1 local mode="$(fix_mode "$conf")" local modified=1 local param name value from for param in "$@"; do splitvar "$param" name value echo "$name${value:+ $value}" >>"$conf" modified=0 done unfix_mode "$conf" "$mode" return $modified } function aconf_array_append() { aconf_append "$@"; } function aconf_check() { # Dans le fichier de configuration $1, tester si tous les paramètres $2..* # sont présents. # Chaque argument de cette fonction correspond à une variable du fichier de # configuration, et doit être de la forme name[=value] # Si une valeur est spécifiée, vérifier que le fichier contient la valeur # correspondante. Sinon, tester uniquement la présence de la directive. local conf="$1"; shift [ -n "$*" ] || return 1 local param name value from for param in "$@"; do splitvar "$param" name value from="^[ $TAB]*$name[ $TAB]*" if [ "$name" != "$param" ]; then from="$from$(quote_seds "$value")\$" fi quietgrep "$from" "$conf" || return 1 done return 0 } ################################################################################ # Gestion des fichiers de configuration où les directives sont placées dans des # sections identifiées par une chaine de la forme [section]. Les directives sont # de la forme 'name=value', et les commentaires débutent par '#' ou ';'. C'est # typiquement le cas des fichiers de configuration de MySQL (my.cnf) et de PHP # (php.ini) function mconf_enable() { # Dans le fichier de configuration $1, activer les paramètres $3..* de la # section $2 # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name[=value] # Toutes les directives de ce nom sont recherchées et décommentées, et la # valeur mise à jour. Si la directive ne figure pas dans le fichier, elle y # est rajoutée à la fin. A cause du mode opératoire, cette fonction ne # convient pas pour les directives dont le nom peut apparaitre plusieurs # fois dans le fichier # Retourner 0 si une modification a été faite dans le fichier, 1 sinon # Cette fonction nécessite gawk et ignore la locale local conf="$1"; shift local section="$1"; shift [ -n "$*" ] || return 1 local script i param name value script="BEGIN { modified = 1 section = $(quoted_awk "$section") in_section = 0" i=0 for param in "$@"; do splitvar "$param" name value script="$script names[$i] = $(quoted_awk "$name")" if [ "$name" != "$param" ]; then script="$script hasvalues[$i] = 1 values[$i] = $(quoted_awk "$value")" else script="$script hasvalues[$i] = 0 values[$i] = \"\"" fi script="$script seen[$i] = 0" let i=$i+1 done script="$script"' } function write_unseen(write_section, wrote_section) { for (i in seen) { if (!seen[i]) { if (write_section && !wrote_section) { print "[" section "]" wrote_section = 1 } print names[i] "=" values[i] seen[i] = 1 modified = 0 } } } /^\[.*\]/ { was_in_section = in_section match($0, /^\[(.*)\]/, vs) in_section = (vs[1] == section) if (!in_section && was_in_section) { write_unseen() } } ' i=0 for param in "$@"; do splitvar "$param" name value script="$script in_section && \$0 ~ /^[ $TAB]*((##*|;;*)[ $TAB]*)?$name[ $TAB]*=/ { \$0 = gensub(/^[ $TAB]*(##*|;;*)[ $TAB]*($name[ $TAB]*=)/, \"\\\\2\", \"\") if (hasvalues[$i]) { \$0 = gensub(/^([ $TAB]*$name[ $TAB]*=[ $TAB]*).*\$/, \"\\\\1\" values[$i], \"\") } seen[$i] = 1 modified = 0 }" let i=$i+1 done script="$script { print } END { write_unseen(!in_section) exit modified }" local tmpfile; ac_set_tmpfile tmpfile local mode="$(fix_mode "$conf")" local modified=1 if <"$conf" >"$tmpfile" cawk "$script"; then cat "$tmpfile" >"$conf" modified=0 fi unfix_mode "$conf" "$mode" ac_clean "$tmpfile" return $modified } function mconf_disable() { # Dans le fichier de configuration $1, désactiver les paramètres $3..* de la # section $2. # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name[=value] # Si la valeur est précisée, la directive correspondant à ce nom et cette # valeur est recherchée et commentée. Sinon, toutes les directives de ce # noms sont recherchées et commentées. Si la directive ne figure pas dans le # fichier, c'est un NOP. # Retourner 0 si une modification a été faite dans le fichier, 1 sinon # Cette fonction nécessite gawk et ignore la locale local conf="$1"; shift local section="$1"; shift [ -n "$*" ] || return 1 local script match script="BEGIN { modified = 1 section = $(quoted_awk "$section") in_section = 0 " local param name value for param in "$@"; do splitvar "$param" name value match="${match:+$match || }\$0 ~ /^[ $TAB]*$name[ $TAB]*=" if [ "$name" != "$param" ]; then match="$match[ $TAB]*$(quote_seds "$value")\$" fi match="$match/" done script="$script }"' /^\[.*\]/ { match($0, /^\[(.*)\]/, vs) in_section = (vs[1] == section) } in_section && ('"$match"') { $0 = "#" $0 modified = 0 } { print } END { exit modified } ' local tmpfile; ac_set_tmpfile tmpfile local mode="$(fix_mode "$conf")" local modified=1 if <"$conf" >"$tmpfile" cawk "$script"; then cat "$tmpfile" >"$conf" modified=0 fi unfix_mode "$conf" "$mode" ac_clean "$tmpfile" return $modified } function mconf_append() { # Dans le fichier de configuration $1, ajouter des directives correspondant # aux paramètres $3..* dans la section $2 # Chaque argument de cette fonction correspond à une directive du fichier de # configuration et doit être de la forme name=value # Une ligne '$name = $value' est ajoutée à la fin de la section, qui est # créée si nécessaire à la fin du fichier de configuration # Retourner 0 si une modification a été faite dans le fichier, 1 sinon # Cette fonction nécessite gawk et ignore la locale local conf="$1"; shift local section="$1"; shift [ -n "$*" ] || return 1 local script match script="BEGIN { modified = 1 section=$(quoted_awk "$section") in_section=0 " i=0 for param in "$@"; do splitvar "$param" name value script="$script names[$i] = $(quoted_awk "$name") values[$i] = $(quoted_awk "$value") seen[$i] = 0" let i=$i+1 done script="$script"' } function write_vars(write_section, wrote_section) { for (i in seen) { if (!seen[i]) { if (write_section && !wrote_section) { print "[" section "]" wrote_section = 1 } print names[i] "=" values[i] seen[i] = 1 modified = 0 } } } /^\[.*\]/ { was_in_section = in_section match($0, /^\[(.*)\]/, vs) in_section = (vs[1] == section) if (!in_section && was_in_section) { write_vars() } } { print } END { write_vars(!in_section) exit modified }' local tmpfile; ac_set_tmpfile tmpfile local mode="$(fix_mode "$conf")" local modified=1 if <"$conf" >"$tmpfile" cawk "$script"; then cat "$tmpfile" >"$conf" modified=0 fi unfix_mode "$conf" "$mode" ac_clean "$tmpfile" return $modified } function mconf_array_append() { mconf_append "$@"; } function mconf_check() { # Dans le fichier de configuration $1, tester si tous les paramètres $3..* # sont présents dans la section $2 # Chaque argument de cette fonction correspond à une variable du fichier de # configuration, et doit être de la forme name[=value] # Si une valeur est spécifiée, vérifier que le fichier contient la valeur # correspondante. Sinon, tester uniquement la présence de la directive. # Cette fonction nécessite gawk et ignore la locale local conf="$1"; shift local section="$1"; shift [ -n "$*" ] || return 1 local script i param name value script="BEGIN { section = $(quoted_awk "$section") in_section = 0" i=0 for param in "$@"; do splitvar "$param" name value script="$script names[$i] = $(quoted_awk "$name") values[$i] = $(quoted_awk "$value") seen[$i] = 0" let i=$i+1 done script="$script"' } /^\[.*\]/ { match($0, /^\[(.*)\]/, vs) in_section = (vs[1] == section) } ' i=0 for param in "$@"; do splitvar "$param" name value script="$script in_section && \$0 ~ /^[ $TAB]*$name[ $TAB]*=" if [ "$name" != "$param" ]; then script="$script$(quote_seds "$value")\$" fi script="$script/ { seen[$i] = 1 }" let i=$i+1 done script="$script END { for (i in seen) { if (!seen[i]) exit 1 } exit 0 }" <"$conf" cawk "$script" } ################################################################################ # Gestion de fichiers de configuration générique function gconf_addline() { # USAGE # gconf_addline configfile -a BEGIN -z END NEWLINE # Dans le fichier de configuration $1, ajouter la ligne NEWLINE entre les lignes # BEGIN et END. # -a BEGIN # Spécifier une expression pour matcher une ligne de type BEGIN. Si # cette option n'est pas spécifiée, on considère que le début de fichier # matche la ligne BEGIN: la ligne NEWLINE est ajoutée dès que possible. # Les lignes sont matchées dans l'ordre, i.e. avec '-a 1 -a 2', il faut # d'abord trouver la ligne 1 puis la ligne 2, sinon, le test n'est pas # concluant. # -t LINE # Si après avoir matché toutes les lignes BEGIN, la ligne LINE est # rencontrée, alors considérer que la ligne à rajouter existe déjà et # qu'il ne faut pas la rajouter de nouveau # -r LINE # Si après avoir matché toutes les lignes BEGIN, la ligne LINE est # rencontrée, alors considérer que la ligne à rajouter existe et qu'il # faut la mettre à jour. Supprimer la ligne existante et la remplacer # par la nouvelle ligne. # -z END # Spécifier une expression pour matcher la ligne de type END. Que cette # option soit ou non spécifiée, on considère toujours que la fin de # fichier matche la ligne END. Ainsi, si END n'est pas trouvée, la ligne # NEWLINE est ajoutée à la fin du fichier. # Dès que la ligne END est rencontrée, et si aucun des tests -t ou -r # n'est concluant, alors ajouter la nouvelle ligne avant celle-ci # -n MAX[=1] # Ajouter au plus MAX occurences de NEWLINE. Après avoir matché END, le # cycle recommence, au plus MAX-1 fois. Utiliser MAX=-1 pour désactiver # la limite # Cette fonction nécessite gawk et ignore la locale # Retourner 0 si l'ajout s'est fait correctement. Retourner 1 si BEGIN n'a # pas été trouvé, et donc aucun ajout n'a été effectué. Retourner 2 si une # erreur quelconque s'est produite eval "$(utools_local)" local -a beginlines newlines local testline replaceline endline max parse_opts "${PRETTYOPTS[@]}" \ -a:,--begin: beginlines \ -t:,--test: testline= \ -r:,--replace: replaceline= \ -z:,--end: endline= \ -n:,--max: max= \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args" return 2 } conf="$1"; shift [ -n "$conf" ] || { eerror "Vous devez spécifier le fichier à modifier" return 2 } [ -f "$conf" ] || { eerror "$conf: fichier introuvable" return 2 } [ $# -gt 0 ] || { eerror "Vous devez spécifier la ligne à rajouter" return 2 } [ -n "$max" ] || max=1 newlines=("$@") # générer le script local script="$(awkdef -f beginlines[@] testline="$testline" replaceline="$replaceline" endline="$endline" max:int="$max" newlines[@])"' BEGIN { searchindex = 1 found = 0 writeline = 1 if (beginlines_count == 0) found = 1 modified = 0 } function writelines_maybe() { if (writeline) { for (i = 1; i <= newlines_count; i++) { print newlines[i] } writeline = 0 modified = 1 } } max != 0 && !found && searchindex <= length(beginlines) && $0 ~ beginlines[searchindex] { searchindex++ if (searchindex > length(beginlines)) { found = 1 } print; next } max != 0 && found && writeline && testline != "" && $0 ~ testline { writeline = 0 print; next } max != 0 && found && writeline && replaceline != "" && $0 ~ replaceline { writelines_maybe() next } max != 0 && found && writeline && endline != "" && $0 ~ endline { writelines_maybe() searchindex = 1 found = 0 writeline = 1 if (max > 0) max-- print; next } { print } END { if (max != 0 && found && writeline) { writelines_maybe() } if (modified) exit 0 else exit 1 } ' edebug "$script" # traiter le fichier local tmpfile; ac_set_tmpfile tmpfile local mode="$(fix_mode "$conf")" local modified=1 if <"$conf" >"$tmpfile" cawk "$script"; then cat "$tmpfile" >"$conf" modified=0 fi unfix_mode "$conf" "$mode" ac_clean "$tmpfile" return $modified }