Ganti variabel lingkungan dalam file dengan nilai aktualnya?

41

Apakah ada cara mudah untuk mengganti / mengevaluasi variabel lingkungan dalam file? Seperti katakanlah saya memiliki file config.xmlyang berisi:

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

... dll. Saya ingin mengganti $INSTANCE_IDdalam file dengan nilai INSTANCE_IDvariabel lingkungan, $SERVICE_NAMEdengan nilai SERVICE_NAMEenv var. Saya tidak akan tahu apriori mana vars lingkungan diperlukan (atau lebih tepatnya, saya tidak ingin harus memperbarui skrip jika seseorang menambahkan variabel lingkungan baru ke file konfigurasi). Terima kasih!

Robert Fraser
sumber
1
Kapan Anda akan melakukan sesuatu dengan file (cat, echo, source, ...) variabel akan diganti dengan nilainya
Costas
Apakah konten file xml ini terserah Anda? Jika demikian, parameter xslt menawarkan cara lain untuk menyuntikkan nilai dan (tidak seperti envsubst dan sejenisnya) menjamin xml terbentuk dengan baik sebagai hasilnya.
kojiro

Jawaban:

69

Anda dapat menggunakan envsubst(bagian dari gnu gettext):

envsubst < infile

akan mengganti variabel lingkungan di file Anda dengan nilainya yang sesuai. Nama-nama variabel harus hanya terdiri dari karakter alfanumerik atau garis bawah ASCII, tidak dimulai dengan angka dan tidak kosong; jika tidak, referensi variabel tersebut diabaikan.


Untuk mengganti hanya variabel lingkungan tertentu, lihat pertanyaan ini.

don_crissti
sumber
1
... kecuali itu tidak diinstal secara default di gambar buruh pelabuhan saya: '- (
Robert Fraser
4
Itu bagus. Gambar Docker harus ringan dan dibuat khusus. Tentu saja Anda selalu dapat menambahkan envsubst ke sana.
kojiro
Atau pergi wadah penuh di atasnya dan masukkan envsubst ke dalam wadah dengan sendirinya. Ini adalah pola umum dan cara hidup jika Anda menggunakan OS seperti Atomic Host, CoreOS, atau RancherOS. Atomic secara khusus bahkan tidak akan membiarkan root memecah dengan sistem file atau apa yang diinstal Anda harus menggunakan wadah.
Kuberchaun
1
Perhatikan bahwa itu tidak akan menggantikan variabel lingkungan "semua", hanya variabel yang namanya cocok ^[[:alpha:]_][[:alnum:]_]*$dengan lokal POSIX.
Stéphane Chazelas
Tampaknya sangat ringkas, namun belum tentu benar dengan semua nilai substitusi. Tampaknya tidak menghormati karakter khusus XML.
EFraim
16

Ini tidak terlalu bagus tetapi berfungsi

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

Jika itu ada dalam skrip shell, akan terlihat seperti:

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

Edit, proposal kedua:

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

Edit, tidak terkait langsung dengan pertanyaan, tetapi jika variabel dibaca dari file:

(. .env && eval "echo \"$(cat config.xml)\"")
hschou
sumber
Masalahnya adalah jika file berisi baris EOF, baris yang tersisa akan dieksekusi sebagai perintah oleh shell. Kita dapat mengubah pemisah menjadi sesuatu yang lebih panjang atau lebih rumit, tetapi masih ada kemungkinan teoritis untuk bertabrakan. Dan seseorang dapat dengan sengaja membuat file dengan pemisah untuk menjalankan perintah.
ilkkachu
OK, coba ini: eval "echo \" $ (cat config.xml) \ ""
hschou
3
Coba letakkan sesuatu seperti "; ls ;"di dalam file dan lakukan evalperintah itu lagi :) Ini masalah yang hampir sama dengan serangan injeksi SQL. Anda harus benar-benar berhati-hati ketika mencampur data dengan kode (dan itulah perintah shell), kecuali Anda benar - benar yakin tidak ada yang mencoba melakukan apa pun untuk mengacaukan hari Anda.
ilkkachu
Tidak. "; Ls;" tidak akan membahayakan.
hschou
3
@hschou, saya pikir ilkkachu berarti `"; ls ;"`- format komentar memakan backticks. Tapi sebenarnya dia harus ada di `ls`sini. Intinya adalah bahwa isi file mengarah ke eksekusi kode arbitrer dan tidak ada yang dapat Anda lakukan.
Gilles 'SANGAT berhenti menjadi jahat'
8

Jika Anda memiliki Perl (tetapi tidak mendapatkan teks dan envsubst), Anda dapat melakukan penggantian sederhana dengan skrip pendek:

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

Saya berasumsi nama variabel hanya akan memiliki huruf besar dan garis bawah, tetapi pola pertama harus mudah diubah sesuai kebutuhan. $ENV{...}referensi lingkungan yang dilihat Perl.

Jika Anda ingin mendukung ${...}sintaks atau melempar kesalahan pada variabel yang tidak disetel, Anda perlu beberapa pekerjaan lagi. Setara gettextdengan envsubstitu adalah:

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

Meskipun saya merasa bahwa memberi makan variabel seperti itu melalui lingkungan proses tampaknya agak rapuh secara umum: Anda tidak dapat menggunakan variabel sewenang-wenang dalam file (karena mereka mungkin memiliki makna khusus), dan beberapa nilai mungkin bisa memiliki setidaknya setengah data sensitif di dalamnya.

ilkkachu
sumber
Lebih suka tidak menggunakan Perl karena itu seharusnya menjadi wadah buruh pelabuhan, tapi itu sepertinya solusi terbaik.
Robert Fraser
2
Lihat juga perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'untuk hanya mengganti variabel yang didefinisikan.
Stéphane Chazelas
1

Bolehkah saya menyarankan skrip saya sendiri untuk ini?

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
sumber
0

Demikian pula dengan jawaban Perl, substitusi variabel lingkungan dapat didelegasikan ke PHP CLI. Ketergantungan pada PHP mungkin atau mungkin tidak dapat diterima tergantung pada tumpukan teknologi yang digunakan.

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

Anda dapat melangkah lebih jauh dan memasukkannya ke dalam skrip yang dapat digunakan kembali, misalnya 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')
);

Penggunaannya akan:

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