Umgebungsvariablen in einer Datei durch ihre tatsächlichen Werte ersetzen?

41

Gibt es eine einfache Möglichkeit, Umgebungsvariablen in einer Datei zu ersetzen / auszuwerten? Angenommen, ich habe eine Datei config.xml, die Folgendes enthält:

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

...etc. Ich möchte $INSTANCE_IDin der Datei den Wert der INSTANCE_IDUmgebungsvariablen $SERVICE_NAMEdurch den Wert der SERVICE_NAMEenv var ersetzen . Ich weiß nicht von vornherein, welche Umgebungsvariablen benötigt werden (oder vielmehr, ich möchte das Skript nicht aktualisieren müssen, wenn jemand der Konfigurationsdatei eine neue Umgebungsvariable hinzufügt). Vielen Dank!

Robert Fraser
quelle
1
Wann werden Sie etwas mit Datei tun (Katze, Echo, Quelle, ...), wird die Variable durch ihren Wert ersetzt
Costas
Liegt der Inhalt dieser XML-Datei bei Ihnen? In diesem Fall bietet parametrisiertes xslt eine andere Möglichkeit, Werte zu injizieren, und garantiert (im Gegensatz zu envsubst und seiner Art) ein wohlgeformtes xml.
Kojiro

Antworten:

69

Sie könnten envsubst(einen Teil von gnu gettext) verwenden:

envsubst < infile

werden die Umgebungsvariablen in Ihrer Datei durch ihren entsprechenden Wert ersetzen. Die Variablennamen dürfen nur aus alphanumerischen Zeichen oder ASCII-Zeichen mit Unterstreichung bestehen, nicht mit einer Ziffer beginnen und nicht leer sein. Andernfalls wird eine solche Variablenreferenz ignoriert.


Informationen zum Ersetzen nur bestimmter Umgebungsvariablen finden Sie in dieser Frage.

don_crissti
quelle
1
... außer es ist nicht standardmäßig in meinem Docker-Image installiert: '- (
Robert Fraser
4
Das ist gut. Docker-Bilder sollten leicht und maßgeschneidert sein. Natürlich kann man immer noch Envsubst hinzufügen.
Kojiro
Oder stellen Sie einen vollen Behälter drauf und legen Sie envsubst ganz von alleine in einen Behälter. Wenn Sie ein Betriebssystem wie Atomic Host, CoreOS oder RancherOS verwenden, ist dies ein gängiges Muster und eine gängige Lebensweise. Atomic lässt nicht einmal root mit dem Dateisystem oder dem, was installiert ist, zu, dass Sie einen Container verwenden müssen.
Kuberchaun
1
Beachten Sie, dass nicht "alle" Umgebungsvariablen ersetzt werden, sondern nur diejenigen, deren Name ^[[:alpha:]_][[:alnum:]_]*$im POSIX-Gebietsschema übereinstimmt .
Stéphane Chazelas
Scheint sehr prägnant zu sein, muss aber nicht bei allen Substitutionswerten stimmen. Es scheint keine XML-Sonderzeichen zu berücksichtigen.
EFraim
16

Das ist nicht sehr schön, aber es funktioniert

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

Wenn es in einem Shell-Skript wäre, würde es so aussehen:

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

Bearbeiten, zweiter Vorschlag:

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

Bearbeiten, nicht streng auf die Frage bezogen, aber bei Variablen, die aus der Datei gelesen werden:

(. .env && eval "echo \"$(cat config.xml)\"")
hschou
quelle
Das Problem dabei ist, dass, wenn die Datei eine Zeile mit enthält EOF, die restlichen Zeilen von der Shell als Befehle ausgeführt werden. Wir könnten das Trennzeichen in etwas längeres oder komplizierteres ändern, aber es gibt immer noch eine theoretische Möglichkeit einer Kollision. Und jemand könnte absichtlich eine Datei mit dem Trennzeichen erstellen, um Befehle auszuführen.
Ilkkachu
OK, versuchen Sie Folgendes: eval "echo \" $ (cat config.xml) \ ""
hschou
3
Versuchen Sie, so etwas wie "; ls ;"in die Datei einzufügen, und evalwiederholen Sie den Befehl :) Dies ist fast das gleiche Problem wie bei SQL-Injection-Angriffen. Sie müssen wirklich vorsichtig sein , wenn die Daten mit dem Code Mischen (und das ist , was Befehle Shell sind), es sei denn , du bist wirklich , wirklich sicher , niemand versucht , bis Sie Ihren Tag etwas zu verwirren zu tun.
Ilkkachu
Nein. "; Ls;" wird nicht schaden.
Hschou
3
@hschou ich denke ilkkachu meinte `"; ls ;"`- die Kommentarformatierung aß die Backticks. Aber eigentlich sollte das nur `ls`hier sein. Der Punkt ist, dass der Inhalt der Datei zur Ausführung von willkürlichem Code führt und Sie nichts dagegen tun können.
Gilles 'SO- hör auf böse zu sein'
8

Wenn Sie zufällig Perl haben (aber nicht gettext und envsubst), können Sie dies einfach durch ein kurzes Skript ersetzen:

$ 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>

Ich nahm an, dass die Variablennamen nur Großbuchstaben und Unterstriche enthalten, aber das erste Muster sollte sich bei Bedarf leicht ändern lassen. $ENV{...}verweist auf die Umgebung, die Perl sieht.

Wenn Sie die ${...}Syntax unterstützen oder einen Fehler auf nicht gesetzte Variablen werfen möchten , müssen Sie noch etwas arbeiten. Eine enge Äquivalent gettext‚s envsubstwäre:

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

Obwohl ich der Meinung bin, dass die Eingabe derartiger Variablen über die Prozessumgebung im Allgemeinen etwas fragwürdig ist: Sie können in den Dateien keine beliebigen Variablen verwenden (da diese möglicherweise eine spezielle Bedeutung haben), und einige der Werte könnten möglicherweise zumindest eine halbe Bedeutung haben. sensible Daten in ihnen.

ilkkachu
quelle
Ich würde Perl lieber nicht verwenden, da es ein Docker-Container sein soll, aber das scheint die beste Lösung zu sein.
Robert Fraser
2
Siehe auch, perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'um nur die definierten Variablen zu ersetzen.
Stéphane Chazelas
1

Darf ich mein eigenes Skript dafür vorschlagen?

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
quelle
0

Ähnlich wie bei der Perl-Antwort kann die Ersetzung von Umgebungsvariablen an die PHP-CLI delegiert werden. Abhängig vom verwendeten Tech-Stack kann die Abhängigkeit von PHP akzeptabel sein oder auch nicht.

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

Sie können noch weiter gehen und es in ein wiederverwendbares Skript einfügen, zum Beispiel 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')
);

Die Verwendung wäre:

envsubst < input.file > output.file
Sergii Shymko
quelle