Заменить переменные среды в файле с их фактическими значениями?

41

Есть ли простой способ заменить / оценить переменные среды в файле? Например, у меня есть файл, config.xmlкоторый содержит:

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

...так далее. Я хочу заменить $INSTANCE_IDв файле значение INSTANCE_IDпеременной окружения $SERVICE_NAMEзначением SERVICE_NAMEenv var. Я априори не буду знать, какие переменные среды нужны (или, скорее, я не хочу обновлять скрипт, если кто-то добавляет новую переменную среды в файл конфигурации). Благодарность!

Роберт Фрейзер
источник
1
Когда вы будете что-то делать с файлом (cat, echo, source, ...), переменная будет заменена на ее значение
Costas
Подходит ли вам содержимое этого XML-файла? Если это так, параметризованный xslt предлагает другой способ ввода значений и (в отличие от envsubst и тому подобного) гарантирует в результате правильно сформированный xml.
Кодзиро

Ответы:

69

Вы можете использовать envsubst(часть gnu gettext):

envsubst < infile

заменит переменные окружения в вашем файле их соответствующими значениями. Имена переменных должны состоять исключительно из буквенно-цифровых символов или символов подчеркивания ASCII, не должны начинаться с цифры и быть непустыми; в противном случае такая ссылка на переменную игнорируется.


Чтобы заменить только определенные переменные среды, см. Этот вопрос.

don_crissti
источник
1
... за исключением того, что он не установлен по умолчанию в моем образе докера: '- (
Роберт Фрейзер
4
Это хорошо. Изображения докера должны быть легкими и сделанными на заказ. Конечно, вы всегда можете добавить к нему envsubst.
Кодзиро
Или перейдите на полный контейнер и поместите envsubst в контейнер самостоятельно. Это обычная модель и образ жизни, если вы используете такие ОС, как Atomic Host, CoreOS или RancherOS. Атомная, в частности, даже не допустит, чтобы корень связывался с файловой системой или тем, что установлено, вы должны использовать контейнер.
Kuberchaun
1
Обратите внимание, что он не заменит «все» переменные среды, только те, чье имя совпадает ^[[:alpha:]_][[:alnum:]_]*$с языковым стандартом POSIX.
Стефан
Кажется, очень кратким, но не обязательно правильно со всеми значениями замещения. Кажется, он не учитывает специальные символы XML.
EFraim
16

Это не очень хорошо, но это работает

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

Если бы это было в сценарии оболочки, это было бы похоже на:

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

Изменить, второе предложение:

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

Редактировать, не строго связанный с вопросом, но в случае чтения переменных из файла:

(. .env && eval "echo \"$(cat config.xml)\"")
hschou
источник
Проблема заключается в том, что если файл содержит строку с EOF, остальные строки будут выполняться как команды оболочкой. Мы могли бы изменить разделитель на что-то более длинное или более сложное, но теоретическая возможность столкновения все еще существует. И кто-то может сознательно сделать файл с разделителем для выполнения команд.
ilkkachu
Хорошо, попробуйте это: eval "echo \" $ (cat config.xml) \ ""
hschou
3
Попробуйте поместить что-то вроде "; ls ;"в файл и evalповторите эту команду :) Это почти та же проблема, что и при атаках с использованием SQL-инъекций. Вы должны быть очень осторожны, когда смешиваете данные с кодом (и это команды оболочки), если только вы действительно не уверены , что никто не пытается сделать что-то, что испортит ваш день.
ilkkachu
№;; ls; " не причинит никакого вреда.
6
3
@hschou Я думаю, что имел в виду ilkkachu `"; ls ;"`- форматирование комментариев сгорело назад . Но на самом деле это должно быть `ls`здесь. Дело в том, что содержимое файла приводит к выполнению произвольного кода, и вы ничего не можете с этим поделать.
Жиль "ТАК - перестань быть злым"
8

Если у вас есть Perl (но не gettext и envsubst), вы можете сделать простую замену коротким скриптом:

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

Я предполагал, что имена переменных будут состоять только из заглавных букв и подчеркиваний, но первый шаблон должен легко изменяться по мере необходимости. $ENV{...}ссылки на среду, которую видит Perl.

Если вы хотите поддержать ${...}синтаксис или выдать ошибку для неустановленных переменных, вам потребуется дополнительная работа. Близкий эквивалент gettext«х envsubstбудет:

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

Хотя я чувствую, что подача таких переменных через среду процесса в целом выглядит немного ненадежно: вы не можете использовать произвольные переменные в файлах (поскольку они могут иметь особое значение), а некоторые значения могут иметь по крайней мере полу конфиденциальные данные в них.

ilkkachu
источник
Не хотел бы использовать Perl, поскольку он должен быть док-контейнером, но это выглядит как лучшее решение.
Роберт Фрейзер
2
Смотрите также, perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'чтобы заменить только переменные, которые определены.
Стефан
1

Могу ли я предложить свой сценарий для этого?

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

Как и в случае ответа Perl, подстановка переменных среды может быть делегирована в CLI PHP. Зависимость от PHP может быть или не быть приемлемой в зависимости от используемого технического стека.

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

Вы можете пойти дальше и поместить его в повторно используемый скрипт, например 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')
);

Использование будет:

envsubst < input.file > output.file
Сергей Шымко
источник