Substituir variáveis ​​de ambiente em um arquivo por seus valores reais?

41

Existe uma maneira fácil de substituir / avaliar variáveis ​​de ambiente em um arquivo? Como, digamos, eu tenho um arquivo config.xmlque contém:

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

... etc Desejo substituir $INSTANCE_IDno arquivo o valor da INSTANCE_IDvariável de ambiente $SERVICE_NAMEpelo valor da SERVICE_NAMEvar env. Não vou saber a priori quais vars de ambiente são necessários (ou melhor, não quero atualizar o script se alguém adicionar uma nova variável de ambiente ao arquivo de configuração). Obrigado!

Robert Fraser
fonte
11
Quando você vai fazer alguma coisa com o arquivo (cat, eco, fonte, ...) a variável vai subtitute por seu valor
Costas
O conteúdo deste arquivo xml é com você? Nesse caso, o xslt parametrizado oferece outra maneira de injetar valores e (ao contrário do envsubst e de seus ilk) garante um xml bem formado como resultado.
Kojiro 09/07

Respostas:

69

Você pode usar envsubst(parte de gnu gettext):

envsubst < infile

substituirá as variáveis ​​de ambiente no seu arquivo pelo valor correspondente. Os nomes das variáveis ​​devem consistir apenas em caracteres ASCII alfanuméricos ou sublinhados, não começar com um dígito e não serem vazios; caso contrário, essa referência variável é ignorada.


Para substituir apenas determinadas variáveis ​​de ambiente, consulte esta pergunta.

don_crissti
fonte
11
... exceto que ele não está instalado por padrão na minha imagem do docker: '- (
Robert Fraser
4
Isso é bom. As imagens do Docker devem ser leves e personalizadas. É claro que você sempre pode adicionar isso a ele.
Kojiro 09/07
Ou coloque um contêiner cheio e coloque o envsubst em um contêiner sozinho. É um padrão comum e um modo de vida se você usar um sistema operacional como Atomic Host, CoreOS ou RancherOS. O Atomic, especificamente, não deixa bagunça no sistema de arquivos ou no que está instalado, você precisa usar um contêiner.
precisa saber é o seguinte
11
Observe que ele não substituirá "todas" as variáveis ​​de ambiente, apenas aquelas cujo nome corresponda ^[[:alpha:]_][[:alnum:]_]*$ao código do idioma POSIX.
Stéphane Chazelas
Parece ser muito sucinto, mas não necessariamente correto com todos os valores de substituição. Parece não respeitar caracteres especiais XML.
EFraim
16

Isso não é muito bom, mas funciona

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

Se estivesse em um script de shell, ficaria assim:

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

Editar, segunda proposta:

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

Edite, não estritamente relacionado à pergunta, mas no caso de variáveis ​​lidas do arquivo:

(. .env && eval "echo \"$(cat config.xml)\"")
hschou
fonte
O problema é que, se o arquivo contiver uma linha com EOF, as linhas restantes serão executadas como comandos pelo shell. Poderíamos mudar o separador para algo mais longo ou mais complicado, mas ainda existe uma possibilidade teórica de colisão. E alguém poderia criar deliberadamente um arquivo com o separador para executar comandos.
21916 ilkkachu
OK, tente o seguinte: eval "echo \" $ (cat config.xml) \ ""
hschou
3
Tente colocar algo como "; ls ;"dentro do arquivo e execute esse evalcomando novamente :) Esse é praticamente o mesmo problema dos ataques de injeção SQL. Você tem que ter muito cuidado ao misturar dados com código (e é isso que comandos shell são), a menos que você é realmente , realmente certeza de que ninguém está a tentar fazer nada para estragar o seu dia.
21916 ilkkachu
Não "; ls;" não fará nenhum mal.
Hschou 9/07
3
@hschou Acho que ilkkachu significava `"; ls ;"`- a formatação do comentário comia as reticências. Mas, na verdade, esse shoule deve estar `ls`aqui. O ponto é que o conteúdo do arquivo leva à execução arbitrária de código e não há nada que você possa fazer sobre isso.
Gilles 'SO- stop be evil'
8

Se você tiver Perl (mas não gettext e envsubst), poderá fazer a substituição simples com um script curto:

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

Supus que os nomes das variáveis ​​tenham apenas letras maiúsculas e sublinhados, mas o primeiro padrão deve ser fácil de alterar conforme necessário. $ENV{...}referencia o ambiente que Perl vê.

Se você deseja dar suporte à ${...}sintaxe ou gerar um erro em variáveis ​​não definidas, precisará de mais trabalho. Um equivalente próximo de gettext's envsubstseria:

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

Embora eu ache que alimentar variáveis ​​como essa através do ambiente do processo parece um pouco duvidoso em geral: você não pode usar variáveis ​​arbitrárias nos arquivos (pois elas podem ter significados especiais), e alguns dos valores podem ter pelo menos semi- dados sensíveis neles.

ilkkachu
fonte
Prefere não usar Perl, pois é um container docker, mas essa parece a melhor solução.
Robert Fraser
2
Consulte também perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'para substituir apenas as variáveis ​​definidas.
Stéphane Chazelas
1

Posso sugerir meu próprio script para isso?

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
Jose San Leandro
fonte
0

De maneira semelhante à resposta Perl, a substituição da variável de ambiente pode ser delegada na CLI do PHP. A dependência do PHP pode ou não ser aceitável, dependendo da pilha de tecnologia em uso.

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

Você pode ir além e colocá-lo em um script reutilizável, por exemplo 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')
);

O uso seria:

envsubst < input.file > output.file
Sergii Shymko
fonte