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


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, не должны начинаться с цифры и быть непустыми; в противном случае такая ссылка на переменную игнорируется.


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


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

Проблема заключается в том, что если файл содержит строку с 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'

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


Не хотел бы использовать 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.