用实际值替换文件中的环境变量?


41

有没有一种简单的方法可以替换/评估文件中的环境变量?假设我有一个config.xml包含以下内容的文件:

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

...等等。我想$INSTANCE_IDINSTANCE_ID环境变量$SERVICE_NAME的值和SERVICE_NAMEenv var 的值替换文件。我不会事先知道需要哪个环境变量(或者,如果有人将新的环境变量添加到配置文件中,我不想更新脚本)。谢谢!


1
您何时将对文件(cat,echo,source等)进行处理,变量将被其值替换
Costas

这个xml文件的内容由您决定吗?如果是这样,则参数化的xslt提供了另一种注入值的方式,并且(不同于envsubst及其同类)确保了格式正确的xml。
kojiro '16

Answers:


69

您可以使用envsubst(的一部分gnu gettext):

envsubst < infile

会将文件中的环境变量替换为其相应的值。变量名称必须仅由字母数字或下划线ASCII字符组成,不能以数字开头且为非空;否则,将忽略此类变量引用。


要仅替换某些环境变量,请参阅此问题。


1
...除了默认情况下未在我的docker映像中安装它:'-(
Robert Fraser

4
非常好。Docker映像应轻巧且量身定制。当然,您始终可以向其添加envsubst。
kojiro '16

或将完整的容器放在其上,然后将envsubst本身全部放入容器中。如果您使用Atomic Host,CoreOS或RancherOS等操作系统,这是一种常见的模式和生活方式。Atomic甚至不会让root干扰文件系统或您必须使用容器安装的内容。
Kuberchaun

1
请注意,它不会替换“所有”环境变量,只会替换名称^[[:alpha:]_][[:alnum:]_]*$在POSIX语言环境中匹配的那些变量。
斯特凡Chazelas

看起来非常简洁,但是不一定对所有替换值都正确。它似乎不尊重XML特殊字符。
EFraim

16

这不是很好,但是可以

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

如果它在shell脚本中,则如下所示:

#! /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,则其余行将由Shell作为命令执行。我们可以将分隔符更改为更长或更复杂的东西,但是在理论上仍有碰撞的可能性。有人可以故意用分隔符制作一个文件来执行命令。
ilkkachu '16

OK,试试看:eval“ echo \” $(cat config.xml)\“”
hschou

3
尝试"; ls ;"在文件中放入类似内容,然后eval再次执行该命令:)这与SQL注入攻击几乎是相同的问题。你有混合编码数据时非常小心(而这正是shell命令的),除非你真的真的确信没有人试图做任何事情搞砸了你的一天。
ilkkachu '16

号“; ls” 不会造成任何伤害。
hschou

3
@hschou我认为ilkkachu的意思是`"; ls ;"`-注释格式吃掉了引号。但是实际上应该在`ls`这里。关键是文件的内容会导致任意代码执行,您对此无能为力。
吉尔斯(Gillles)“所以-别再邪恶了”

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看到的环境。

如果要支持${...}语法或对未设置的变量抛出错误,则需要做更多的工作。与gettextenvsubst近似值是:

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

尽管我觉得通过过程环境来提供这样的变量通常看起来有些麻烦:您不能在文件中使用任意变量(因为它们可能具有特殊含义),并且某些值可能至少具有半值,其中的敏感数据。


宁愿不使用Perl,因为它应该是docker容器,但这似乎是最好的解决方案。
罗伯特·弗雷泽

2
另请参见perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'仅替换定义的变量。
斯特凡Chazelas

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.