Sostituire le variabili di ambiente in un file con i loro valori effettivi?


41

Esiste un modo semplice per sostituire / valutare le variabili di ambiente in un file? Come diciamo che ho un file config.xmlche contiene:

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

...eccetera. Voglio sostituire $INSTANCE_IDnel file il valore della INSTANCE_IDvariabile d'ambiente, $SERVICE_NAMEcon il valore di SERVICE_NAMEvar env. Non saprò a priori quali variabili di ambiente sono necessarie (o meglio, non voglio aggiornare lo script se qualcuno aggiunge una nuova variabile di ambiente al file di configurazione). Grazie!


1
Quando farai qualcosa con file (cat, echo, source, ...) la variabile verrà sottotitolata dal suo valore
Costas

Il contenuto di questo file XML dipende da te? In tal caso, xslt con parametri offre un altro modo per iniettare valori e (a differenza di envsubst e dei suoi simili) garantisce di conseguenza xml ben formato.
Kojiro,

Risposte:


69

È possibile utilizzare envsubst(parte di gnu gettext):

envsubst < infile

sostituirà le variabili di ambiente nel tuo file con il loro valore corrispondente. I nomi delle variabili devono essere costituiti esclusivamente da caratteri alfanumerici o di sottolineatura ASCII, non devono iniziare con una cifra ed essere vuoti; in caso contrario, tale riferimento di variabile viene ignorato.


Per sostituire solo alcune variabili di ambiente, vedere questa domanda.


1
... tranne che non è installato di default nell'immagine della mia finestra mobile: '- (
Robert Fraser,

4
Quello è buono. Le immagini docker devono essere leggere e realizzate su misura. Ovviamente, puoi sempre aggiungere envsubst ad esso.
Kojiro,

Oppure vai su un contenitore pieno e metti tutto in un contenitore da solo. È un modello comune e uno stile di vita se si utilizza un sistema operativo come Atomic Host, CoreOS o RancherOS. Atomic, in particolare, non lascerà nemmeno il caos di root con il file system o ciò che è installato, è necessario utilizzare un contenitore.
Kuberchaun,

1
Si noti che non sostituirà "tutte" le variabili di ambiente, solo quelle il cui nome corrisponde ^[[:alpha:]_][[:alnum:]_]*$nella locale POSIX.
Stéphane Chazelas,

Sembra essere molto conciso, tuttavia non necessariamente corretto con tutti i valori di sostituzione. Non sembra rispettare i caratteri speciali XML.
EFraim,

16

Questo non è molto bello ma funziona

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

Se fosse in uno script di shell sembrerebbe:

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

Modifica, seconda proposta:

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

Modifica, non strettamente correlato alla domanda, ma in caso di variabili lette dal file:

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

Il problema è che se il file contiene una riga con EOF, le righe rimanenti verranno eseguite come comandi dalla shell. Potremmo cambiare il separatore in qualcosa di più lungo o più complicato, ma c'è ancora una possibilità teorica di scontrarsi. E qualcuno potrebbe deliberatamente creare un file con il separatore per eseguire i comandi.
ilkkachu,

OK, prova questo: eval "echo \" $ (cat config.xml) \ ""
hschou

3
Prova a inserire qualcosa di simile "; ls ;"all'interno del file ed evalesegui nuovamente quel comando :) Questo è praticamente lo stesso problema degli attacchi di iniezione SQL. Devi stare molto attento quando mescoli i dati con il codice (ed è quello che sono i comandi della shell), a meno che tu non sia davvero , davvero sicuro che nessuno stia cercando di fare qualcosa per rovinare la tua giornata.
ilkkachu,

No. "; ls;" non farà alcun male.
hschou,

3
@hschou Penso che ilkkachu significasse `"; ls ;"`: la formattazione del commento ha mangiato i backtick. Ma in realtà quella shoule è proprio `ls`qui. Il punto è che il contenuto del file porta all'esecuzione di codice arbitrario e non c'è nulla che tu possa fare al riguardo.
Gilles 'SO- smetti di essere malvagio' il

8

Se ti capita di avere Perl (ma non gettext e envsubst) puoi fare la semplice sostituzione con uno script breve:

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

Presumo che i nomi delle variabili avranno solo lettere maiuscole e caratteri di sottolineatura, ma il primo modello dovrebbe essere facile da modificare secondo necessità. $ENV{...}fa riferimento all'ambiente che Perl vede.

Se vuoi supportare la ${...}sintassi o lanciare un errore su variabili non impostate, avrai bisogno di altro lavoro. Un equivalente stretto di gettext's envsubstsarebbe:

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

Anche se ritengo che alimentare variabili del genere tramite l'ambiente di processo in generale appaia un po 'incerto: non è possibile utilizzare variabili arbitrarie nei file (poiché potrebbero avere significati speciali) e alcuni dei valori potrebbero possedere almeno semi- dati sensibili in essi contenuti.


Preferirei non usare Perl poiché dovrebbe essere un contenitore docker, ma sembra la soluzione migliore.
Robert Fraser,

2
Vedi anche perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'per sostituire solo le variabili definite.
Stéphane Chazelas,

1

Posso suggerire la mia sceneggiatura per questo?

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

Analogamente alla risposta Perl, la sostituzione delle variabili d'ambiente può essere delegata alla CLI di PHP. La dipendenza da PHP può essere o non essere accettabile a seconda dello stack tecnologico in uso.

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

Puoi andare oltre e inserirlo in uno script riutilizzabile, ad esempio 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')
);

L'utilizzo sarebbe:

envsubst < input.file > output.file
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.