แทนที่ตัวแปรสภาพแวดล้อมในไฟล์ด้วยค่าจริงหรือไม่?


41

มีวิธีง่าย ๆ ในการทดแทน / ประเมินตัวแปรสภาพแวดล้อมในไฟล์หรือไม่? อย่างสมมติว่าฉันมีไฟล์config.xmlที่มี:

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

... ฯลฯ ฉันต้องการแทนที่$INSTANCE_IDในไฟล์ด้วยค่าของINSTANCE_IDตัวแปรสภาพแวดล้อม$SERVICE_NAMEด้วยค่าของSERVICE_NAMEenv var ฉันไม่ทราบว่าจะต้องใช้ vars รุ่นใด (หรือมากกว่านั้นฉันไม่ต้องการอัปเดตสคริปต์หากมีคนเพิ่มตัวแปรสภาพแวดล้อมใหม่ลงในไฟล์ปรับแต่ง) ขอบคุณ!


1
เมื่อใดที่คุณจะทำอะไรกับไฟล์ (cat, echo, source, …) ตัวแปรจะลบล้างค่าของมัน
Costas

เนื้อหาของไฟล์ xml นี้ขึ้นอยู่กับคุณหรือไม่? ถ้าเป็นเช่นนั้น parameterized xslt เสนอวิธีอื่นในการฉีดค่าและ (ต่างจาก envsubst และ ilk) รับประกัน xml ที่มีรูปแบบที่ดีตามมา
kojiro

คำตอบ:


69

คุณสามารถใช้envsubst(ส่วนหนึ่งของgnu gettext):

envsubst < infile

จะแทนที่ตัวแปรสภาพแวดล้อมในไฟล์ของคุณด้วยค่าที่สอดคล้องกัน ชื่อตัวแปรจะต้องประกอบด้วยอักขระ ASCII และตัวอักษรหรือขีดล่างเท่านั้นโดยไม่เริ่มต้นด้วยตัวเลขและไม่ใช่แบบเว้นวรรค มิฉะนั้นการอ้างอิงตัวแปรจะถูกละเว้น


หากต้องการแทนที่ตัวแปรสภาพแวดล้อมบางตัวเท่านั้นโปรดดูคำถามนี้


1
... ยกเว้นจะไม่มีการติดตั้งตามค่าเริ่มต้นในอิมเมจนักเทียบท่าของฉัน: '- (
Robert Fraser

4
ดีแล้ว. ภาพนักเทียบท่าควรมีน้ำหนักเบาและปรับแต่งได้ แน่นอนว่าคุณสามารถเพิ่ม envsubst ลงไปได้เสมอ
kojiro

หรือไปที่ภาชนะเต็มแล้วใส่ envsubst ลงในคอนเทนเนอร์ทั้งหมด มันเป็นรูปแบบทั่วไปและวิถีชีวิตถ้าคุณใช้ประโยชน์จากระบบปฏิบัติการเช่น Atomic Host, CoreOS หรือ RancherOS อะตอมมิกจะไม่ปล่อยให้รูทเลอะกับระบบไฟล์หรือติดตั้งอะไรคุณต้องใช้คอนเทนเนอร์
Kuberchaun

1
โปรดทราบว่ามันจะไม่แทนที่ตัวแปรสภาพแวดล้อม "ทั้งหมด" เท่านั้นที่มีชื่อตรงกับ^[[:alpha:]_][[:alnum:]_]*$ในโลแคล POSIX
Stéphane Chazelas

ดูเหมือนจะรวบรัดมาก แต่ไม่จำเป็นต้องถูกต้องกับค่าทดแทนทั้งหมด ดูเหมือนจะไม่เคารพอักขระพิเศษ XML
EFraim

16

มันไม่ได้ดีมาก แต่ใช้งานได้

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

หากอยู่ในเชลล์สคริปต์มันจะมีลักษณะดังนี้:

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

แก้ไขข้อเสนอที่สอง:

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

แก้ไขไม่เกี่ยวข้องกับคำถามอย่างเคร่งครัด แต่ในกรณีที่ตัวแปรอ่านจากไฟล์:

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

ปัญหานี้คือถ้าไฟล์มีบรรทัดด้วยบรรทัดEOFที่เหลือจะถูกดำเนินการตามคำสั่งโดยเชลล์ เราสามารถเปลี่ยนตัวแยกเป็นอะไรที่ยาวหรือซับซ้อนกว่านี้ได้ แต่ยังมีความเป็นไปได้ทางทฤษฎีที่จะชนกัน และบางคนจงใจทำไฟล์ด้วยตัวคั่นเพื่อดำเนินการคำสั่ง
ilkkachu

ตกลงลองสิ่งนี้: eval "echo \" $ (cat config.xml) \ ""
hschou

3
ลองใส่สิ่งที่ต้องการ"; ls ;"ภายในไฟล์และทำevalคำสั่งนั้นอีกครั้ง :) นี่เป็นปัญหาเดียวกับการโจมตีด้วยการฉีด SQL คุณจะต้องระมัดระวังจริงๆเมื่อผสมข้อมูลด้วยรหัส (และนั่นคือสิ่งที่เปลือกคำสั่งที่มี) ถ้าคุณจริงๆ , จริงๆแน่ใจว่าไม่มีใครพยายามที่จะทำอะไรที่จะเลอะวันของคุณ
ilkkachu

ไม่ "; ls;" จะไม่ทำอันตรายใด ๆ
hschou

3
@hschou ฉันคิดว่า ilkkachu หมายถึง`"; ls ;"`- การจัดรูปแบบความคิดเห็นกิน backticks แต่ที่จริงแล้ว shoule อยู่`ls`ที่นี่ ประเด็นก็คือเนื้อหาของไฟล์จะนำไปสู่การใช้รหัสโดยอำเภอใจและไม่มีอะไรที่คุณสามารถทำได้
Gilles 'หยุดความชั่วร้าย'

8

หากคุณมี Perl (แต่ไม่ใช่ gettext และenvsubst) คุณสามารถทำการแทนที่แบบง่าย ๆ ด้วยสคริปต์สั้น ๆ :

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

ฉันสันนิษฐานว่าชื่อตัวแปรจะมีตัวอักษรตัวพิมพ์ใหญ่และเครื่องหมายขีดล่างเท่านั้น แต่รูปแบบแรกควรเปลี่ยนได้ง่ายตามต้องการ $ENV{...}อ้างอิงถึงสภาพแวดล้อมที่ Perl เห็น

หากคุณต้องการสนับสนุน${...}ไวยากรณ์หรือมีข้อผิดพลาดในตัวแปรที่ไม่ได้ตั้งค่าคุณจะต้องทำงานเพิ่มเติม เทียบเท่าใกล้ชิดของgettext's envsubstจะเป็น:

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

แม้ว่าฉันรู้สึกว่าการป้อนตัวแปรแบบนั้นผ่านสภาพแวดล้อมของกระบวนการดูเหมือนว่าไม่แน่นอนโดยทั่วไป: คุณไม่สามารถใช้ตัวแปรตามอำเภอใจในไฟล์ได้ (เนื่องจากอาจมีความหมายพิเศษ) และค่าบางอย่างอาจมีอย่างน้อยกึ่ง ข้อมูลที่สำคัญในพวกเขา


จะไม่ใช้ Perl เพราะมันควรจะเป็นคอนเทนเนอร์นักเทียบท่า แต่ดูเหมือนว่าทางออกที่ดีที่สุด
Robert Fraser

2
ดูเพิ่มเติมperl -pe 's{\$(\{)?(\w+)(?(1)\})}{$ENV{$2} // $&}ge'เพื่อแทนที่ตัวแปรที่กำหนดไว้เท่านั้น
Stéphane Chazelas

1

ฉันขอแนะนำสคริปต์ของตัวเองได้ไหม

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

ในทำนองเดียวกันกับคำตอบ Perl การแทนที่ตัวแปรสภาพแวดล้อมสามารถมอบให้กับ PHP CLI การพึ่งพา PHP อาจขึ้นอยู่กับการยอมรับหรือไม่ก็ได้ขึ้นอยู่กับเทคโนโลยีที่ใช้งาน

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

คุณสามารถไปต่อและวางไว้ในสคริปต์ที่ใช้ซ้ำได้ตัวอย่างเช่น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')
);

การใช้งานจะเป็น:

envsubst < input.file > output.file
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.