Thay thế các biến môi trường trong một tập tin bằng các giá trị thực tế của chúng?


41

Có cách nào dễ dàng để thay thế / đánh giá các biến môi trường trong một tệp không? Giống như giả sử tôi có một tệp config.xmlchứa:

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

... Vv Tôi muốn thay thế $INSTANCE_IDtrong tệp bằng giá trị của INSTANCE_IDbiến môi trường, $SERVICE_NAMEbằng giá trị của SERVICE_NAMEvar env. Tôi sẽ không biết một ưu tiên mà các vars môi trường là cần thiết (hay đúng hơn, tôi không muốn phải cập nhật tập lệnh nếu ai đó thêm một biến môi trường mới vào tệp cấu hình). Cảm ơn!


1
Khi nào bạn sẽ làm một cái gì đó với tệp (cat, echo, source, Mạnh), biến sẽ thay thế theo giá trị của nó
Costas

Là nội dung của tập tin xml này tùy thuộc vào bạn? Nếu vậy, xslt được tham số hóa cung cấp một cách khác để tiêm các giá trị và (không giống như envsubst và ilk của nó) đảm bảo xml được hình thành tốt như là kết quả.
kojiro

Câu trả lời:


69

Bạn có thể sử dụng envsubst(một phần của gnu gettext):

envsubst < infile

sẽ thay thế các biến môi trường trong tệp của bạn bằng giá trị tương ứng của chúng. Tên biến phải chỉ bao gồm các ký tự ASCII chữ và số, gạch dưới, không bắt đầu bằng một chữ số và không trống; mặt khác, một tham chiếu biến như vậy được bỏ qua.


Để chỉ thay thế các biến môi trường nhất định, xem câu hỏi này.


1
... ngoại trừ nó không được cài đặt theo mặc định trong hình ảnh docker của tôi: '- (
Robert Fraser

4
Điều đó thật tốt. Docker hình ảnh nên được nhẹ và thiết kế riêng. Tất nhiên, bạn luôn có thể thêm envsubst vào nó.
Kojiro

Hoặc đi đến container đầy đủ trên đó và đặt envsubst trong một container. Đó là một mô hình phổ biến và cách sống nếu bạn sử dụng một hệ điều hành như Máy chủ nguyên tử, CoreOS hoặc RancherOS. Nguyên tử cụ thể thậm chí sẽ không để root gây rối với hệ thống tệp hoặc những gì được cài đặt mà bạn phải sử dụng một thùng chứa.
Kuberchaun

1
Lưu ý rằng nó sẽ không thay thế các biến môi trường "tất cả", chỉ những biến có tên khớp với ^[[:alpha:]_][[:alnum:]_]*$ngôn ngữ POSIX.
Stéphane Chazelas

Có vẻ rất cô đọng, tuy nhiên không nhất thiết phải đúng với tất cả các giá trị thay thế. Nó dường như không tôn trọng các ký tự đặc biệt XML.
EFraim

16

Điều này không tốt lắm nhưng nó hoạt động

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

Nếu nó nằm trong tập lệnh shell thì nó sẽ trông như sau:

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

Chỉnh sửa, đề xuất thứ hai:

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

Chỉnh sửa, không liên quan chặt chẽ đến câu hỏi, nhưng trong trường hợp các biến được đọc từ tệp:

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

Vấn đề với điều này là nếu tệp chứa một dòng với EOF, các dòng còn lại sẽ được thực thi dưới dạng các lệnh của shell. Chúng ta có thể thay đổi dải phân cách thành một cái gì đó dài hơn hoặc phức tạp hơn, nhưng vẫn có khả năng về mặt lý thuyết là va chạm. Và ai đó có thể cố tình tạo một tệp với dấu phân cách để thực thi các lệnh.
ilkkachu

OK, hãy thử điều này: eval "echo \" $ (cat config.xml) \ ""
hschou

3
Hãy thử đặt một cái gì đó như "; ls ;"bên trong tệp và thực hiện evallại lệnh đó :) Đây là vấn đề khá giống với các cuộc tấn công tiêm nhiễm SQL. Bạn phải thực sự cẩn thận khi trộn dữ liệu với mã (và đó là lệnh shell), trừ khi bạn thực sự , thực sự chắc chắn rằng không ai đang cố làm bất cứ điều gì để làm xáo trộn ngày của bạn.
ilkkachu

Số "; ls;" sẽ không làm hại gì
hschou

3
@hschou Tôi nghĩ ilkkachu có nghĩa là `"; ls ;"`- định dạng bình luận đã ăn các backticks. Nhưng thật ra cái shoule đó chỉ `ls`ở đây. Vấn đề là nội dung của tệp dẫn đến việc thực thi mã tùy ý và bạn không thể làm gì về nó.
Gilles 'SO- đừng trở nên xấu xa'

8

Nếu bạn tình cờ có Perl (nhưng không phải là gettext và envsubst), bạn có thể thực hiện thay thế đơn giản bằng một đoạn script ngắn:

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

Tôi giả sử tên biến sẽ chỉ có chữ in hoa và dấu gạch dưới, nhưng mẫu đầu tiên sẽ dễ dàng thay đổi khi cần thiết. $ENV{...}tài liệu tham khảo môi trường Perl thấy.

Nếu bạn muốn hỗ trợ ${...}cú pháp hoặc đưa ra lỗi về các biến không đặt, bạn sẽ cần thêm một số công việc. Một tương đương gần với gettext's envsubstsẽ là:

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

Mặc dù tôi cảm thấy rằng việc cung cấp các biến như thế thông qua môi trường quy trình có vẻ hơi iffy nói chung: bạn không thể sử dụng các biến tùy ý trong các tệp (vì chúng có thể có ý nghĩa đặc biệt) và một số giá trị có thể có ít nhất là bán dữ liệu nhạy cảm trong đó.


Thà không sử dụng Perl vì nó được coi là một container docker, nhưng đó có vẻ là giải pháp tốt nhất.
Robert Fraser

2
Xem thêm perl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'để chỉ thay thế các biến được xác định.
Stéphane Chazelas

1

Tôi có thể đề nghị kịch bản của riêng tôi cho điều này?

https://github.com/rydnr/set-sapes/blob/master/.temsheet/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

Tương tự như câu trả lời Perl, sự thay thế biến môi trường có thể được ủy quyền cho CLI PHP. Sự phụ thuộc vào PHP có thể hoặc không thể được chấp nhận tùy thuộc vào ngăn xếp công nghệ đang sử dụng.

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

Bạn có thể đi xa hơn và đặt nó trong một tập lệnh có thể sử dụng lại, ví dụ 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')
);

Việc sử dụng sẽ là:

envsubst < input.file > output.file
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.