Замінити змінні середовища у файлі їх фактичними значеннями?


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 та його ilk) гарантує добре сформований xml у результаті.
kojiro

Відповіді:


69

Ви можете використовувати envsubst(частину gnu gettext):

envsubst < infile

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


Щоб замінити лише певні змінні середовища, див. Це питання.


1
... за винятком того, що він не встановлений за замовчуванням у моєму докерському зображенні: '- (
Роберт Фрейзер

4
Добре. Зображення докера повинні бути легкими та виготовленими під замовлення. Звичайно, ви завжди можете додати envsubst до цього, хоча.
kojiro

Або перейдіть на нього повний контейнер і покладіть envsubst в контейнер все сам. Це загальна схема та спосіб життя, якщо ви користуєтесь такою ОС, як Atomic Host, CoreOS або RancherOS. Atomic спеціально навіть не дозволить кореневі возитися з файловою системою або для того, що встановлено, ви повинні використовувати контейнер.
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;" не принесе шкоди.
hschou

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{...}посилання на середовище, яке бачить Перл.

Якщо ви хочете підтримати ${...}синтаксис або нанести помилку на невстановлені змінні, вам знадобиться ще деяка робота. Близьким еквівалентом gettext's 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, заміна змінної середовища може бути делегована на PHP CLI. Залежність від 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.