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.