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!


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.


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)\"")

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.


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.