Remplacer les variables d'environnement dans un fichier avec leurs valeurs réelles?

41

Existe-t-il un moyen simple de substituer / évaluer des variables d’environnement dans un fichier? Comme disons que j'ai un fichier config.xmlqui contient:

<property>
    <name>instanceId</name>
    <value>$INSTANCE_ID</value>
</property>
<property>
    <name>rootPath</name>
    <value>/services/$SERVICE_NAME</value>
</property>

...etc. Je veux remplacer $INSTANCE_IDdans le fichier par la valeur de la INSTANCE_IDvariable d'environnement, $SERVICE_NAMEpar la valeur de la variable SERVICE_NAMEenv. Je ne saurai pas a priori quels vars d'environnement sont nécessaires (ou plutôt, je ne veux pas avoir à mettre à jour le script si quelqu'un ajoute une nouvelle variable d'environnement au fichier de configuration). Merci!

Robert Fraser
la source
1
Quand ferez-vous quelque chose avec file (cat, echo, source,…), la variable sous-titrera par sa valeur
Costas
Le contenu de ce fichier XML dépend-il de vous? Si tel est le cas, xslt paramétré offre un autre moyen d'injecter des valeurs et (contrairement à envsubst et ses semblables) garantit un xml bien formé.
kojiro

Réponses:

69

Vous pourriez utiliser envsubst(une partie de gnu gettext):

envsubst < infile

remplacera les variables d’environnement de votre fichier par la valeur correspondante. Les noms de variable doivent contenir uniquement des caractères alphanumériques ou des caractères de soulignement ASCII, ne pas commencer par un chiffre ni être vide. sinon, une telle référence de variable est ignorée.


Pour ne remplacer que certaines variables d'environnement, reportez-vous à cette question.

don_crissti
la source
1
... sauf que ce n'est pas installé par défaut dans mon image de menu fixe: '- (
Robert Fraser
4
C'est bon. Les images Docker doivent être légères et faites sur mesure. Bien sûr, vous pouvez toujours y ajouter envsubst.
kojiro
Ou bien remplissez un conteneur et mettez envsubst dans un conteneur tout seul. C'est un schéma courant et un mode de vie si vous utilisez un système d'exploitation comme Atomic Host, CoreOS ou RancherOS. En particulier, Atomic ne laissera pas la racine en désordre avec le système de fichiers ou avec ce qui est installé, vous devez utiliser un conteneur.
Kuberchaun
1
Notez qu'il ne remplacera pas "toutes" les variables d'environnement, mais uniquement celles dont le nom correspond ^[[:alpha:]_][[:alnum:]_]*$aux paramètres régionaux POSIX.
Stéphane Chazelas
Semble être très succinct, mais pas nécessairement correct avec toutes les valeurs de substitution. Il ne semble pas respecter les caractères spéciaux XML.
Début
16

Ce n'est pas très gentil mais ça marche

( echo "cat <<EOF" ; cat config.xml ; echo EOF ) | sh

Si c'était dans un script shell, cela ressemblerait à:

#! /bin/sh
cat <<EOF
<property>
    <name>instanceId</name>
    <value>$INSTANCE_ID</value>
</property>
EOF

Modifier, deuxième proposition:

eval "echo \"$(cat config.xml)\""

Éditer, pas strictement lié à la question, mais dans le cas de variables lues à partir du fichier:

(. .env && eval "echo \"$(cat config.xml)\"")
hschou
la source
Le problème avec ceci est que si le fichier contient une ligne avec EOF, les lignes restantes seront exécutées en tant que commandes par le shell. Nous pourrions changer le séparateur en quelque chose de plus long ou de plus compliqué, mais il existe toujours une possibilité théorique de collision. Et quelqu'un pourrait délibérément créer un fichier avec le séparateur pour exécuter des commandes.
ilkkachu
OK, essayez ceci: eval "echo \" $ (cat config.xml) \ ""
hschou
3
Essayez de mettre quelque chose comme "; ls ;"dans le fichier et refaites cette evalcommande :) C'est à peu près le même problème que pour les attaques par injection SQL. Vous devez être très prudent lors du mélange des données avec le code (et ce qui est des commandes shell sont), sauf si vous êtes vraiment , vraiment sûr que personne ne tente de faire quoi que ce soit à gâcher votre journée.
ilkkachu
Non "; ls;" ne fera aucun mal.
hschou
3
@hschou Je pense qu'il voulait dire `"; ls ;"`- le formatage des commentaires a mangé les backticks. Mais en réalité, il devrait être juste `ls`ici. Le fait est que le contenu du fichier entraîne l'exécution de code arbitraire et que vous ne pouvez rien y faire.
Gilles 'SO- arrête d'être méchant'
8

Si vous avez Perl (mais pas gettext et envsubst), vous pouvez effectuer le remplacement simple avec un court script:

$ export INSTANCE_ID=foo; export SERVICE_NAME=bar;
$ perl -pe 's/\$([_A-Z]+)/$ENV{$1}/g'  < config.xml
<property>
    <name>instanceId</name>
    <value>foo</value>
</property>
<property>
    <name>rootPath</name>
    <value>/services/bar</value>
</property>

J'ai supposé que les noms de variables n'auraient que des lettres majuscules et des traits de soulignement, mais le premier motif devrait être facile à modifier si nécessaire. $ENV{...}fait référence à l'environnement que Perl voit.

Si vous souhaitez prendre en charge la ${...}syntaxe ou générer une erreur sur des variables non définies, vous aurez encore du travail à faire. Un proche équivalent de gettext's envsubstserait:

perl -pe 's/\$(\{)?([a-zA-Z_]\w*)(?(1)\})/$ENV{$2}/g'

Bien que j’ai le sentiment que le fait de nourrir de telles variables via l’environnement de processus semble un peu incertain en général: vous ne pouvez pas utiliser de variables arbitraires dans les fichiers (car elles peuvent avoir une signification particulière), et certaines des valeurs pourraient avoir au moins une valeur semi-automatique. données sensibles en eux.

ilkkachu
la source
Je préférerais ne pas utiliser Perl car il est supposé être un conteneur de menu fixe, mais cela semble être la meilleure solution.
Robert Fraser
2
Voir aussi perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'pour ne substituer que les variables définies.
Stéphane Chazelas
1

Puis-je suggérer mon propre script pour cela?

https://github.com/rydnr/set-square/blob/master/.templates/common-files/process-file.sh

#!/bin/bash /usr/local/bin/dry-wit
# Copyright 2016-today Automated Computing Machinery S.L.
# Distributed under the terms of the GNU General Public License v3

function usage() {
cat <<EOF
$SCRIPT_NAME -o|--output output input
$SCRIPT_NAME [-h|--help]
(c) 2016-today Automated Computing Machinery S.L.
    Distributed under the terms of the GNU General Public License v3

Processes a file, replacing any placeholders with the contents of the
environment variables, and stores the result in the specified output file.

Where:
    * input: the input file.
    * output: the output file.
Common flags:
    * -h | --help: Display this message.
    * -v: Increase the verbosity.
    * -vv: Increase the verbosity further.
    * -q | --quiet: Be silent.
EOF
}

# Requirements
function checkRequirements() {
  checkReq envsubst ENVSUBST_NOT_INSTALLED;
}

# Error messages
function defineErrors() {
  export INVALID_OPTION="Unrecognized option";
  export ENVSUBST_NOT_INSTALLED="envsubst is not installed";
  export NO_INPUT_FILE_SPECIFIED="The input file is mandatory";
  export NO_OUTPUT_FILE_SPECIFIED="The output file is mandatory";

  ERROR_MESSAGES=(\
    INVALID_OPTION \
    ENVSUBST_NOT_INSTALLED \
    NO_INPUT_FILE_SPECIFIED \
    NO_OUTPUT_FILE_SPECIFIED \
  );

  export ERROR_MESSAGES;
}

## Parses the input
## dry-wit hook
function parseInput() {

  local _flags=$(extractFlags $@);
  local _flagCount;
  local _currentCount;

  # Flags
  for _flag in ${_flags}; do
    _flagCount=$((_flagCount+1));
    case ${_flag} in
      -h | --help | -v | -vv | -q)
         shift;
         ;;
      -o | --output)
         shift;
         OUTPUT_FILE="${1}";
         shift;
         ;;
    esac
  done

  # Parameters
  if [[ -z ${INPUT_FILE} ]]; then
    INPUT_FILE="$1";
    shift;
  fi
}

## Checking input
## dry-wit hook
function checkInput() {

  local _flags=$(extractFlags $@);
  local _flagCount;
  local _currentCount;
  logDebug -n "Checking input";

  # Flags
  for _flag in ${_flags}; do
    _flagCount=$((_flagCount+1));
    case ${_flag} in
      -h | --help | -v | -vv | -q | --quiet)
         ;;
      -o | --output)
         ;;
      *) logDebugResult FAILURE "fail";
         exitWithErrorCode INVALID_OPTION ${_flag};
         ;;
    esac
  done

  if [[ -z ${INPUT_FILE} ]]; then
    logDebugResult FAILURE "fail";
    exitWithErrorCode NO_INPUT_FILE_SPECIFIED;
  fi

  if [[ -z ${OUTPUT_FILE} ]]; then
      logDebugResult FAILURE "fail";
      exitWithErrorCode NO_OUTPUT_FILE_SPECIFIED;
  fi
}

## Replaces any placeholders in given file.
## -> 1: The file to process.
## -> 2: The output file.
## <- 0 if the file is processed, 1 otherwise.
## <- RESULT: the path of the processed file.
function replace_placeholders() {
  local _file="${1}";
  local _output="${2}";
  local _rescode;
  local _env="$(IFS=" \t" env | awk -F'=' '{printf("%s=\"%s\" ", $1, $2);}')";
  local _envsubstDecl=$(echo -n "'"; IFS=" \t" env | cut -d'=' -f 1 | awk '{printf("${%s} ", $0);}'; echo -n "'";);

  echo "${_env} envsubst ${_envsubstDecl} < ${_file} > ${_output}" | sh;
  _rescode=$?;
  export RESULT="${_output}";
  return ${_rescode};
}

## Main logic
## dry-wit hook
function main() {
  replace_placeholders "${INPUT_FILE}" "${OUTPUT_FILE}";
}
# vim: syntax=sh ts=2 sw=2 sts=4 sr noet
Jose San Leandro
la source
0

De la même manière que pour la réponse Perl, la substitution de variable d’environnement peut être déléguée à la CLI de PHP. La dépendance à PHP peut être ou ne pas être acceptable en fonction de la pile technologique utilisée.

php -r 'echo preg_replace_callback("/\\$([a-z0-9_]+)/i", function ($matches) { return getenv($matches[1]); }, fread(STDIN, 8192));' < input.file > output.file

Vous pouvez aller plus loin et le mettre dans un script réutilisable, par exemple envsubst:

#!/usr/bin/env php
<?php

echo preg_replace_callback(
    '/\$(?<name>[a-z0-9_]+)/i',
    function ($matches) {
        return getenv($matches['name']);
    },
    file_get_contents('php://stdin')
);

L'utilisation serait:

envsubst < input.file > output.file
Sergii Shymko
la source