Zastąpić zmienne środowiskowe w pliku ich rzeczywistymi wartościami?

41

Czy istnieje prosty sposób na zamianę / ocenę zmiennych środowiskowych w pliku? Powiedzmy, że mam plik config.xmlzawierający:

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

...itp. Chcę zastąpić $INSTANCE_IDw pliku wartością INSTANCE_IDzmiennej środowiskowej $SERVICE_NAMEwartością SERVICE_NAMEenv var. Nie będę z góry wiedział, które zmienne środowiskowe są potrzebne (a raczej nie chcę aktualizować skryptu, jeśli ktoś doda nową zmienną środowiskową do pliku konfiguracyjnego). Dzięki!

Robert Fraser
źródło
1
Kiedy zrobisz coś z plikiem (cat, echo, source,…) zmienna podda się wartością
Costas,
Czy zawartość tego pliku XML zależy od Ciebie? Jeśli tak, sparametryzowany xslt oferuje inny sposób wstrzykiwania wartości i (w przeciwieństwie do envsubst i jego ilk) gwarantuje w rezultacie dobrze sformatowany xml.
kojiro

Odpowiedzi:

69

Możesz użyć envsubst(część gnu gettext):

envsubst < infile

zastąpi zmienne środowiskowe w twoim pliku odpowiadającą im wartością. Nazwy zmiennych muszą składać się wyłącznie ze znaków alfanumerycznych lub znaków podkreślenia ASCII, nie mogą zaczynać się cyfrą i być niepuste; w przeciwnym razie takie odwołanie do zmiennej jest ignorowane.


Aby zastąpić tylko niektóre zmienne środowiskowe, zobacz to pytanie.

don_crissti
źródło
1
... z wyjątkiem tego, że nie jest domyślnie zainstalowany na obrazie dokera: '- (
Robert Fraser
4
Dobre. Obrazy dokerów powinny być lekkie i wykonane na zamówienie. Oczywiście zawsze można do tego dodać envsubst.
kojiro
Lub przejdź na pełny pojemnik i sam umieść envsubst w pojemniku. Jest to powszechny wzorzec i styl życia, jeśli korzystasz z systemu operacyjnego, takiego jak Atomic Host, CoreOS lub RancherOS. Atomic specjalnie nie pozwoli nawet rootowi zepsuć się z systemem plików lub z tym, co jest zainstalowane, musisz użyć kontenera.
Kuberchaun
1
Zauważ, że nie zastąpi „wszystkich” zmiennych środowiskowych, tylko tych, których nazwa pasuje ^[[:alpha:]_][[:alnum:]_]*$do ustawień narodowych POSIX.
Stéphane Chazelas
Wydaje się być bardzo zwięzły, ale niekoniecznie poprawny ze wszystkimi wartościami podstawienia. Wygląda na to, że nie szanuje znaków specjalnych XML.
EFraim,
16

To nie jest bardzo miłe, ale działa

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

Gdyby był w skrypcie powłoki, wyglądałby następująco:

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

Edycja, druga propozycja:

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

Edytuj, nie ściśle związane z pytaniem, ale w przypadku zmiennych odczytanych z pliku:

(. .env && eval "echo \"$(cat config.xml)\"")
hschou
źródło
Problem polega na tym, że jeśli plik zawiera wiersz z EOF, pozostałe wiersze zostaną wykonane przez powłokę jako polecenia. Możemy zmienić separator na coś dłuższego lub bardziej skomplikowanego, ale wciąż istnieje teoretyczna możliwość kolizji. I ktoś mógłby celowo utworzyć plik z separatorem, aby wykonać polecenia.
ilkkachu
OK, spróbuj tego: eval "echo \" $ (cat config.xml) \ ""
hschou
3
Spróbuj umieścić coś "; ls ;"w pliku i evalpowtórz to polecenie :) Jest to prawie taki sam problem jak w przypadku ataków typu SQL injection. Musisz być bardzo ostrożny podczas mieszania danych z kodem (i takie są polecenia powłoki), chyba że naprawdę , naprawdę jesteś pewien , że nikt nie próbuje zrobić nic, co by zepsuło ci dzień.
ilkkachu
Nie. "; Ls;" nie wyrządzi żadnej szkody.
hschou
3
@hschou Myślę, że ilkkachu miał na myśli `"; ls ;"`- formatowanie komentarzy zjadło backticks. Ale tak naprawdę ten shoule `ls`tu będzie. Chodzi o to, że zawartość pliku prowadzi do wykonania dowolnego kodu i nic nie możesz na to poradzić.
Gilles „SO- przestań być zły”,
8

Jeśli zdarzy ci się mieć Perla (ale nie gettext i envsubst), możesz wykonać proste zastąpienie krótkim skryptem:

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

Zakładałem, że nazwy zmiennych będą zawierać tylko wielkie litery i podkreślenia, ale pierwszy wzór powinien być łatwy do zmiany w razie potrzeby. $ENV{...}odwołuje się do środowiska, które widzi Perl.

Jeśli chcesz wesprzeć ${...}składnię lub rzucić błąd na nieustawione zmienne, będziesz potrzebować trochę więcej pracy. Bliskie odpowiednikiem gettext„s envsubstbyłoby:

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

Chociaż wydaje mi się, że podawanie takich zmiennych za pośrednictwem środowiska procesowego wydaje się nieco niepewne: w plikach nie można używać dowolnych zmiennych (ponieważ mogą mieć specjalne znaczenie), a niektóre wartości mogą mieć przynajmniej pół- wrażliwe dane w nich zawarte.

ilkkachu
źródło
Wolałbym nie używać Perla, ponieważ ma to być kontener dokujący, ale to wygląda na najlepsze rozwiązanie.
Robert Fraser
2
Zobacz także, perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'aby zastąpić tylko zdefiniowane zmienne.
Stéphane Chazelas
1

Czy mogę zasugerować własny skrypt do tego?

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
źródło
0

Podobnie jak odpowiedź Perla, podstawianie zmiennych środowiskowych może być delegowane do CLI PHP. Zależność od PHP może, ale nie musi być akceptowalna, w zależności od stosu technologii.

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

Możesz pójść dalej i umieścić go w skrypcie wielokrotnego użytku, na przykład 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')
);

Wykorzystanie byłoby:

envsubst < input.file > output.file
Sergii Shymko
źródło