Bash Templating: Làm thế nào để xây dựng các tệp cấu hình từ các mẫu với Bash?


134

Tôi đang viết một tập lệnh để tự động tạo các tệp cấu hình cho Apache và PHP cho máy chủ web của riêng tôi. Tôi không muốn sử dụng bất kỳ GUI nào như CPanel hoặc ISPConfig.

Tôi có một số mẫu tệp cấu hình Apache và PHP. Tập lệnh Bash cần đọc các mẫu, thực hiện thay thế biến và xuất các mẫu được phân tích cú pháp vào một số thư mục. Cách tốt nhất để làm điều đó là gì? Tôi có thể nghĩ ra nhiều cách. Cái nào là tốt nhất hoặc có thể có một số cách tốt hơn để làm điều đó? Tôi muốn làm điều đó trong Bash thuần túy (ví dụ dễ dàng trong PHP)

1) Làm cách nào để thay thế chỗ giữ $ {} trong tệp văn bản?

template.txt:

the number is ${i}
the word is ${word}

script.sh:

#!/bin/sh

#set variables
i=1
word="dog"
#read in template one line at the time, and replace variables
#(more natural (and efficient) way, thanks to Jonathan Leffler)
while read line
do
    eval echo "$line"
done < "./template.txt"

BTW, làm cách nào để chuyển hướng đầu ra sang tệp bên ngoài ở đây? Tôi có cần phải thoát một cái gì đó nếu các biến chứa, nói, trích dẫn?

2) Sử dụng cat & sed để thay thế từng biến bằng giá trị của nó:

Đã cho template.txt:

The number is ${i}
The word is ${word}

Chỉ huy:

cat template.txt | sed -e "s/\${i}/1/" | sed -e "s/\${word}/dog/"

Có vẻ xấu đối với tôi vì cần phải thoát khỏi nhiều biểu tượng khác nhau và với nhiều biến số, dòng này sẽ dài ra.

Bạn có thể nghĩ về một số giải pháp thanh lịch và an toàn khác?


Điều này có trả lời câu hỏi của bạn không? Làm cách nào để thay thế chỗ dành sẵn $ {} trong tệp văn bản?
Ben Leggiero

Câu trả lời:


61

Bạn có thể sử dụng điều này:

perl -p -i -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : $&/eg' < template.txt

để thay thế tất cả các ${...}chuỗi bằng các biến môi trường tương ứng (đừng quên xuất chúng trước khi chạy tập lệnh này).

Đối với bash thuần, nó sẽ hoạt động (giả sử rằng các biến không chứa chuỗi $ {...}):

#!/bin/bash
while read -r line ; do
    while [[ "$line" =~ (\$\{[a-zA-Z_][a-zA-Z_0-9]*\}) ]] ; do
        LHS=${BASH_REMATCH[1]}
        RHS="$(eval echo "\"$LHS\"")"
        line=${line//$LHS/$RHS}
    done
    echo "$line"
done

. Giải pháp không treo nếu RHS tham chiếu một số biến tham chiếu chính nó:

#!/bin/bash
line="$(cat; echo -n a)"
end_offset=${#line}
while [[ "${line:0:$end_offset}" =~ (.*)(\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})(.*) ]] ; do
    PRE="${BASH_REMATCH[1]}"
    POST="${BASH_REMATCH[4]}${line:$end_offset:${#line}}"
    VARNAME="${BASH_REMATCH[3]}"
    eval 'VARVAL="$'$VARNAME'"'
    line="$PRE$VARVAL$POST"
    end_offset=${#PRE}
done
echo -n "${line:0:-1}"

CẢNH BÁO : Tôi không biết cách xử lý chính xác đầu vào với NUL trong bash hoặc bảo toàn số lượng dòng mới. Biến thể cuối cùng được trình bày vì đó là do shell shell tình yêu nhập vào nhị phân:

  1. read sẽ giải thích dấu gạch chéo ngược.
  2. read -r sẽ không giải thích dấu gạch chéo ngược, nhưng vẫn sẽ bỏ dòng cuối cùng nếu nó không kết thúc bằng một dòng mới.
  3. "$(…)"sẽ tước như nhiều dòng mới theo sau như có mặt, vì vậy tôi kết thúc với ; echo -n avà sử dụng echo -n "${line:0:-1}": này giảm xuống ký tự cuối cùng (đó là a) và bảo tồn càng nhiều newlines dấu như đã có trong đầu vào (bao gồm cả không).

3
Tôi sẽ thay đổi [^}]thành [A-Za-Z_][A-Za-z0-9_]phiên bản bash để ngăn shell vượt ra ngoài sự thay thế nghiêm ngặt (ví dụ nếu nó cố xử lý ${some_unused_var-$(rm -rf $HOME)}).
Chris Johnsen

2
@FractalizeR bạn có thể muốn thay đổi $&trong giải pháp perl thành "": lần đầu tiên ${...}không được chạm tới nếu nó không thể thay thế, lần thứ hai thay thế nó bằng chuỗi rỗng.
ZyX

5
LƯU Ý: Rõ ràng có một sự thay đổi từ bash 3.1 thành 3.2 (trở lên) trong đó các trích dẫn đơn xung quanh biểu thức chính - coi nội dung của biểu thức chính là một chuỗi ký tự. Vì vậy, biểu thức chính ở trên phải là ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/iêu
Blue Waters

2
Để làm cho whilevòng lặp đọc dòng cuối cùng ngay cả khi nó không bị chấm dứt bởi một dòng mới, hãy sử dụng while read -r line || [[ -n $line ]]; do. Ngoài ra, readdải lệnh của bạn dẫn đầu và theo dõi khoảng trắng từ mỗi dòng; để tránh điều đó, hãy sử dụngwhile IFS= read -r line || [[ -n $line ]]; do
mkuity0

2
Chỉ cần lưu ý một ràng buộc cho những người tìm kiếm một giải pháp toàn diện: Những giải pháp tiện dụng này không cho phép bạn bảo vệ có chọn lọc các tham chiếu biến đổi khỏi sự mở rộng (chẳng hạn như bằng cách định \ nghĩa chúng).
mkuity0

138

Thử envsubst

FOO=foo
BAR=bar
export FOO BAR

envsubst <<EOF
FOO is $FOO
BAR is $BAR
EOF

11
Chỉ để tham khảo, envsubstkhông bắt buộc khi sử dụng heredoc vì bash coi heredoc là một chuỗi trích dẫn hai chữ và đã nội suy các biến trong đó. Đó là một lựa chọn tuyệt vời khi bạn muốn đọc mẫu từ một tệp khác. Một thay thế tốt cho nhiều rườm rà hơn m4.
beporter

2
Tôi đã rất ngạc nhiên khi biết về lệnh này. Tôi đã cố gắng sử dụng chức năng của envsubst một cách thủ công nhưng không thành công. Cảm ơn yottatsa!
Tim Stewart

4
Lưu ý: envsubstlà một tiện ích gettext GNU và thực sự không phải là tất cả mạnh mẽ (vì gettext có nghĩa là để bản địa hóa các thông điệp của con người). Quan trọng nhất, nó không nhận ra các thay thế $ {VAR} thoát dấu gạch chéo ngược (vì vậy bạn không thể có một mẫu sử dụng các thay thế $ VAR trong thời gian chạy, như tập lệnh shell hoặc tệp conf Nginx). Xem câu trả lời của tôi cho một giải pháp xử lý thoát dấu gạch chéo ngược.
Stuart P. Bentley

4
@beporter Trong trường hợp này, nếu bạn muốn chuyển mẫu này sang envsubst vì một số lý do, bạn muốn sử dụng <<"EOF", không nội suy các biến (các thuật ngữ được trích dẫn giống như dấu ngoặc đơn của heredocs).
Stuart P. Bentley

2
Tôi đã sử dụng nó như sau: cat template.txt | envsubst
Truthadjustr

47

envsubst là mới đối với tôi. Tuyệt diệu.

Đối với bản ghi, sử dụng heredoc là một cách tuyệt vời để tạo mẫu tệp conf.

STATUS_URI="/hows-it-goin";  MONITOR_IP="10.10.2.15";

cat >/etc/apache2/conf.d/mod_status.conf <<EOF
<Location ${STATUS_URI}>
    SetHandler server-status
    Order deny,allow
    Deny from all
    Allow from ${MONITOR_IP}
</Location>
EOF

1
tôi thích điều này tốt hơn envsubstvì nó đã cứu tôi khỏi phần bổ sung apt-get install gettext-basetrong Dockerfile của tôi
Truthadjustr

Tuy nhiên, shell như một tập lệnh giống như Mẫu mà không có bất kỳ cài đặt thư viện bên ngoài nào cũng như không phải đối phó với các biểu thức phức tạp.
千 木

35

Tôi đồng ý với việc sử dụng sed: nó là công cụ tốt nhất để tìm kiếm / thay thế. Đây là cách tiếp cận của tôi:

$ cat template.txt
the number is ${i}
the dog's name is ${name}

$ cat replace.sed
s/${i}/5/
s/${name}/Fido/

$ sed -f replace.sed template.txt > out.txt

$ cat out.txt
the number is 5
the dog's name is Fido

1
Điều này đòi hỏi tập tin tạm thời cho chuỗi thay thế, phải không? Có cách nào để làm điều đó mà không cần các tập tin tạm thời?
Vladislav Rastrusny

@FractalizeR: Một số phiên bản của sed-itùy chọn (chỉnh sửa tệp tại chỗ) tương tự như tùy chọn perl . Kiểm tra trang web cho sed của bạn .
Chris Johnsen

@FractalizeR Có, sed -i sẽ thay thế nội tuyến. Nếu bạn cảm thấy thoải mái với Tcl (một ngôn ngữ kịch bản khác), thì hãy xem chủ đề này: stackoverflow.com/questions/2818130/ Kẻ
Hai Vũ

Tôi đã tạo thay thế từ một propertyfiles whit lệnh sed sau: sed -e 's / ^ / s \ / $ {/ g' -e 's / = /} \ // g' -e 's / $ / \ // g 'the.properies> thay.sed
Jaap D

Mã của @hai vu tạo ra một chương trình sed và vượt qua chương trình đó bằng cách sử dụng cờ của sed. Nếu bạn muốn, thay vào đó bạn có thể chuyển từng dòng của chương trình sed vào sed bằng cách sử dụng cờ -e. FWIW Tôi thích ý tưởng sử dụng sed để tạo khuôn mẫu.
Andrew Allbright

23

Tôi nghĩ eval hoạt động thực sự tốt. Nó xử lý các mẫu với ngắt dòng, khoảng trắng và tất cả các loại công cụ bash. Tất nhiên, nếu bạn có toàn quyền kiểm soát các mẫu:

$ cat template.txt
variable1 = ${variable1}
variable2 = $variable2
my-ip = \"$(curl -s ifconfig.me)\"

$ echo $variable1
AAA
$ echo $variable2
BBB
$ eval "echo \"$(<template.txt)\"" 2> /dev/null
variable1 = AAA
variable2 = BBB
my-ip = "11.22.33.44"

Phương pháp này nên được sử dụng cẩn thận, tất nhiên, vì eval có thể thực thi mã tùy ý. Chạy cái này như root là khá nhiều câu hỏi. Báo giá trong mẫu cần phải được thoát, nếu không chúng sẽ bị ăn theo eval.

Bạn cũng có thể sử dụng ở đây tài liệu nếu bạn thích catđểecho

$ eval "cat <<< \"$(<template.txt)\"" 2> /dev/null

@plockc đã giải thích một giải pháp tránh vấn đề thoát bash quote:

$ eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

Chỉnh sửa: Đã xóa một phần về việc chạy này dưới dạng root bằng sudo ...

Chỉnh sửa: Đã thêm nhận xét về cách trích dẫn cần được thoát, thêm giải pháp của plockc vào hỗn hợp!


Dải này trích dẫn bạn có trong mẫu của bạn và sẽ không thay thế bên trong các trích dẫn đơn, do đó tùy thuộc vào định dạng mẫu của bạn, có thể dẫn đến các lỗi tinh vi. Điều này có lẽ có thể áp dụng cho bất kỳ phương pháp tạo khuôn mẫu dựa trên Bash, mặc dù.
Alex B

Các mẫu dựa trên IMHO Bash là điên rồ, vì bạn cần phải là một lập trình viên bash để hiểu mẫu của bạn đang làm gì! Nhưng cảm ơn vì nhận xét!
mogsie

@AlexB: Cách tiếp cận này sẽ thay thế giữa các dấu ngoặc đơn, vì chúng chỉ là các ký tự bằng chữ bên trong chuỗi trích dẫn kép kèm theo chứ không phải là dấu phân cách chuỗi khi evaled echo / catlệnh xử lý chúng; thử eval "echo \"'\$HOME'\"".
mkuity0

21

Tôi có một giải pháp bash như mogsie nhưng với heredoc thay vì herestring để cho phép bạn tránh thoát dấu ngoặc kép

eval "cat <<EOF
$(<template.txt)
EOF
" 2> /dev/null

4
Giải pháp này hỗ trợ mở rộng tham số Bash trong mẫu. Yêu thích của tôi là các tham số bắt buộc với ${param:?}và lồng văn bản xung quanh các tham số tùy chọn. Ví dụ: ${DELAY:+<delay>$DELAY</delay>}mở rộng thành không có gì khi DELAY không được xác định và <delay> 17 </ delay> khi DELAY = 17.
Eric Bolinger

2
Oh! Và dấu phân cách EOF có thể sử dụng một chuỗi động, giống như PID _EOF_$$.
Eric Bolinger

1
@ mkuity0 Một cách giải quyết khác để theo dõi các dòng mới là sử dụng một số mở rộng như ví dụ một biến trống $trailing_newlinehoặc sử dụng $NL5và đảm bảo rằng nó được mở rộng thành 5 dòng mới.
xebeche

@xebeche: Có, đặt những gì bạn đề xuất ở cuối cùng bên trongtemplate.txt sẽ hoạt động để duy trì các dòng mới.
mkuity0

1
Một giải pháp tinh tế, nhưng lưu ý rằng việc thay thế lệnh sẽ loại bỏ bất kỳ dòng mới nào từ tệp đầu vào, mặc dù điều đó thường không phải là một vấn đề. Một trường hợp cạnh khác: do sử dụng eval, nếu template.txtchứa EOFtrên một dòng của chính nó, nó sẽ chấm dứt sớm tài liệu ở đây và do đó phá vỡ lệnh. (Mẹo đội mũ đến @xebeche).
mkuity0

18

Chỉnh sửa ngày 6 tháng 1 năm 2017

Tôi cần giữ dấu ngoặc kép trong tệp cấu hình của mình để thoát hai dấu ngoặc kép với sed giúp:

render_template() {
  eval "echo \"$(sed 's/\"/\\\\"/g' $1)\""
}

Tôi không thể nghĩ đến việc giữ các dòng mới, nhưng các dòng trống ở giữa được giữ.


Mặc dù đây là một chủ đề cũ, IMO tôi đã tìm ra giải pháp thanh lịch hơn ở đây: http://pempek.net/articles/2013/07/08/bash-sh-as-template-engine/

#!/bin/sh

# render a template configuration file
# expand variables + preserve formatting
render_template() {
  eval "echo \"$(cat $1)\""
}

user="Gregory"
render_template /path/to/template.txt > path/to/configuration_file

Tất cả các khoản tín dụng cho Grégory Pakosz .


Điều này loại bỏ dấu ngoặc kép từ đầu vào và, nếu có nhiều dòng mới trong tệp đầu vào, thay thế chúng bằng một dấu ngoặc đơn.
mkuity0

2
Tôi cần hai dấu gạch chéo ngược ít hơn để làm cho nó hoạt động, tức là, eval "echo \"$(sed 's/\"/\\"/g' $1)\""
David Bau

Thật không may, cách tiếp cận này không cho phép bạn tạo mẫu tệp php (chúng chứa $variables).
IStranger

10

Thay vì phát minh lại bánh xe đi với envsubst Có thể được sử dụng trong hầu hết mọi trường hợp, ví dụ: xây dựng các tệp cấu hình từ các biến môi trường trong các thùng chứa docker.

Nếu trên mac hãy chắc chắn rằng bạn đã có homebrew rồi liên kết nó từ gettext:

brew install gettext
brew link --force gettext

./template.cfg

# We put env variables into placeholders here
this_variable_1 = ${SOME_VARIABLE_1}
this_variable_2 = ${SOME_VARIABLE_2}

./.env:

SOME_VARIABLE_1=value_1
SOME_VARIABLE_2=value_2

./có hình.sh

#!/bin/bash
cat template.cfg | envsubst > whatever.cfg

Bây giờ chỉ cần sử dụng nó:

# make script executable
chmod +x ./configure.sh
# source your variables
. .env
# export your variables
# In practice you may not have to manually export variables 
# if your solution depends on tools that utilise .env file 
# automatically like pipenv etc. 
export SOME_VARIABLE_1 SOME_VARIABLE_2
# Create your config file
./configure.sh

trình tự gọi này envsubstthực sự hoạt động.
Alex

Đối với bất kỳ ai khác đang tìm kiếm, envsubstkhông hoạt động trên MacOS, bạn cần cài đặt nó bằng homebrew : brew install gettext.
Matt

9

Một phiên bản dài hơn nhưng mạnh mẽ hơn của câu trả lời được chấp nhận:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\})?;substr($1,0,int(length($1)/2)).($2&&length($1)%2?$2:$ENV{$3||$4});eg' template.txt

Điều này mở rộng tất cả các trường hợp $VAR hoặc ${VAR} đến các giá trị môi trường của chúng (hoặc, nếu chúng không được xác định, chuỗi trống).

Nó thoát đúng dấu gạch chéo ngược và chấp nhận dấu gạch chéo ngược thoát $ để ức chế thay thế (không giống như envsubst, hóa ra, nó không làm điều này ).

Vì vậy, nếu môi trường của bạn là:

FOO=bar
BAZ=kenny
TARGET=backslashes
NOPE=engi

và mẫu của bạn là:

Two ${TARGET} walk into a \\$FOO. \\\\
\\\$FOO says, "Delete C:\\Windows\\System32, it's a virus."
$BAZ replies, "\${NOPE}s."

kết quả sẽ là:

Two backslashes walk into a \bar. \\
\$FOO says, "Delete C:\Windows\System32, it's a virus."
kenny replies, "${NOPE}s."

Nếu bạn chỉ muốn thoát dấu gạch chéo ngược trước $ (bạn có thể viết "C: \ Windows \ System32" trong một mẫu không thay đổi), hãy sử dụng phiên bản sửa đổi một chút này:

perl -pe 's;(\\*)(\$([a-zA-Z_][a-zA-Z_0-9]*)|\$\{([a-zA-Z_][a-zA-Z_0-9]*)\});substr($1,0,int(length($1)/2)).(length($1)%2?$2:$ENV{$3||$4});eg' template.txt

8

Tôi đã làm theo cách này, có thể ít hiệu quả hơn, nhưng dễ đọc / bảo trì hơn.

TEMPLATE='/path/to/template.file'
OUTPUT='/path/to/output.file'

while read LINE; do
  echo $LINE |
  sed 's/VARONE/NEWVALA/g' |
  sed 's/VARTWO/NEWVALB/g' |
  sed 's/VARTHR/NEWVALC/g' >> $OUTPUT
done < $TEMPLATE

10
Bạn có thể làm điều này mà không cần đọc từng dòng một và chỉ với một lời mời sed:sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Brandon Bloom

8

Nếu bạn muốn sử dụng các mẫu Jinja2 , hãy xem dự án này: j2cli .

Nó hỗ trợ:

  • Mẫu từ các tệp JSON, INI, YAML và các luồng đầu vào
  • Tạo mẫu từ các biến môi trường

5

Lấy câu trả lời từ ZyX bằng cách sử dụng bash thuần túy nhưng với kết hợp regex kiểu mới và thay thế tham số gián tiếp, nó trở thành:

#!/bin/bash
regex='\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}'
while read line; do
    while [[ "$line" =~ $regex ]]; do
        param="${BASH_REMATCH[1]}"
        line=${line//${BASH_REMATCH[0]}/${!param}}
    done
    echo $line
done

5

Nếu sử dụng Perl là một tùy chọn và bạn hài lòng với việc chỉ mở rộng dựa trên các biến môi trường (trái ngược với tất cả các biến vỏ ), hãy xem xét câu trả lời mạnh mẽ của Stuart P. Bentley .

Câu trả lời này nhằm mục đích cung cấp một giải pháp chỉ bash mà - mặc dù sử dụng eval- nên an toàn khi sử dụng .

Các mục tiêu là:

  • Hỗ trợ mở rộng cả hai ${name}$nametham chiếu biến.
  • Ngăn chặn tất cả các mở rộng khác:
    • thay thế lệnh ( $(...)và cú pháp kế thừa`...` )
    • thay thế số học ( $((...))và cú pháp kế thừa $[...]).
  • Cho phép triệt tiêu có chọn lọc việc mở rộng biến bằng cách thêm tiền tố vào \(\${name} ).
  • Bảo quản ký tự đặc biệt. trong đầu vào, đáng chú ý "và các \trường hợp.
  • Cho phép đầu vào thông qua đối số hoặc thông qua stdin.

Chức năngexpandVars() :

expandVars() {
  local txtToEval=$* txtToEvalEscaped
  # If no arguments were passed, process stdin input.
  (( $# == 0 )) && IFS= read -r -d '' txtToEval
  # Disable command substitutions and arithmetic expansions to prevent execution
  # of arbitrary commands.
  # Note that selectively allowing $((...)) or $[...] to enable arithmetic
  # expressions is NOT safe, because command substitutions could be embedded in them.
  # If you fully trust or control the input, you can remove the `tr` calls below
  IFS= read -r -d '' txtToEvalEscaped < <(printf %s "$txtToEval" | tr '`([' '\1\2\3')
  # Pass the string to `eval`, escaping embedded double quotes first.
  # `printf %s` ensures that the string is printed without interpretation
  # (after processing by by bash).
  # The `tr` command reconverts the previously escaped chars. back to their
  # literal original.
  eval printf %s "\"${txtToEvalEscaped//\"/\\\"}\"" | tr '\1\2\3' '`(['
}

Ví dụ:

$ expandVars '\$HOME="$HOME"; `date` and $(ls)'
$HOME="/home/jdoe"; `date` and $(ls)  # only $HOME was expanded

$ printf '\$SHELL=${SHELL}, but "$(( 1 \ 2 ))" will not expand' | expandVars
$SHELL=/bin/bash, but "$(( 1 \ 2 ))" will not expand # only ${SHELL} was expanded
  • Vì lý do hiệu năng, hàm đọc đầu vào stdin cùng một lúc vào bộ nhớ, nhưng thật dễ dàng để điều chỉnh chức năng theo cách tiếp cận từng dòng.
  • Cũng hỗ trợ các mở rộng biến không cơ bản${HOME:0:10} , miễn là chúng không chứa lệnh nhúng hoặc thay thế số học, chẳng hạn như${HOME:0:$(echo 10)}
    • Các thay thế nhúng như vậy thực sự BREAK chức năng (bởi vì tất cả $(và các `trường hợp được thoát một cách mù quáng).
    • Tương tự, các tham chiếu biến không đúng định dạng như ${HOME(thiếu đóng }) BREAK hàm.
  • Do xử lý chuỗi trích dẫn kép của bash, dấu gạch chéo ngược được xử lý như sau:
    • \$name ngăn chặn sự mở rộng.
    • Một \không theo sau $được bảo tồn như là.
    • Nếu bạn muốn đại diện cho nhiều \ trường hợp liền kề , bạn phải nhân đôi chúng ; ví dụ:
      • \\-> \- giống như chỉ\
      • \\\\ -> \\
    • Ký tự đầu vào không được chứa những điều sau đây (hiếm khi được sử dụng), được sử dụng cho mục đích nội: 0x1, 0x2, 0x3.
  • Có một mối quan tâm lớn về mặt giả thuyết rằng nếu bash nên đưa ra cú pháp mở rộng mới, chức năng này có thể không ngăn được việc mở rộng như vậy - xem bên dưới để biết giải pháp không sử dụng eval.

Nếu bạn đang tìm kiếm một giải pháp hạn chế hơn chỉ hỗ trợ ${name}mở rộng - nghĩa là với các dấu ngoặc nhọn bắt buộc , bỏ qua các $nametham chiếu - hãy xem câu trả lời này của tôi.


Đây là phiên bản cải tiến của evalgiải pháp miễn phí chỉ dành cho bash từ câu trả lời được chấp nhận :

Những cải tiến là:

  • Hỗ trợ mở rộng cả hai ${name}$nametham chiếu biến.
  • Hỗ trợ \tham chiếu biến -escaping không nên mở rộng.
  • Không giống như evalgiải pháp dựa trên,
    • mở rộng không cơ bản được bỏ qua
    • tham chiếu biến không đúng định dạng được bỏ qua (chúng không phá vỡ tập lệnh)
 IFS= read -d '' -r lines # read all input from stdin at once
 end_offset=${#lines}
 while [[ "${lines:0:end_offset}" =~ (.*)\$(\{([a-zA-Z_][a-zA-Z_0-9]*)\}|([a-zA-Z_][a-zA-Z_0-9]*))(.*) ]] ; do
      pre=${BASH_REMATCH[1]} # everything before the var. reference
      post=${BASH_REMATCH[5]}${lines:end_offset} # everything after
      # extract the var. name; it's in the 3rd capture group, if the name is enclosed in {...}, and the 4th otherwise
      [[ -n ${BASH_REMATCH[3]} ]] && varName=${BASH_REMATCH[3]} || varName=${BASH_REMATCH[4]}
      # Is the var ref. escaped, i.e., prefixed with an odd number of backslashes?
      if [[ $pre =~ \\+$ ]] && (( ${#BASH_REMATCH} % 2 )); then
           : # no change to $lines, leave escaped var. ref. untouched
      else # replace the variable reference with the variable's value using indirect expansion
           lines=${pre}${!varName}${post}
      fi
      end_offset=${#pre}
 done
 printf %s "$lines"

5

Đây là một giải pháp bash tinh khiết khác:

  • đó là sử dụng heredoc, vì vậy:
    • độ phức tạp không tăng vì cú pháp yêu cầu bổ sung
    • mẫu có thể bao gồm mã bash
      • Điều đó cũng cho phép bạn thụt lề đúng cách. Xem bên dưới.
  • Nó không sử dụng eval, vì vậy:
    • không có vấn đề gì với việc hiển thị các dòng trống
    • không có vấn đề với dấu ngoặc kép trong mẫu

$ cat code

#!/bin/bash
LISTING=$( ls )

cat_template() {
  echo "cat << EOT"
  cat "$1"
  echo EOT
}

cat_template template | LISTING="$LISTING" bash

$ cat template (với dòng mới và dấu ngoặc kép)

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
$( echo "$LISTING" | sed 's/^/        /' )
      <pre>
    </p>
  </body>
</html>

đầu ra

<html>
  <head>
  </head>
  <body> 
    <p>"directory listing"
      <pre>
        code
        template
      <pre>
    </p>
  </body>
</html>

4

Đây là một giải pháp khác: tạo tập lệnh bash với tất cả các biến và nội dung của tệp mẫu, tập lệnh đó sẽ trông như thế này:

word=dog           
i=1                
cat << EOF         
the number is ${i} 
the word is ${word}

EOF                

Nếu chúng ta đưa tập lệnh này vào bash, nó sẽ tạo ra đầu ra mong muốn:

the number is 1
the word is dog

Đây là cách tạo tập lệnh đó và đưa tập lệnh đó vào bash:

(
    # Variables
    echo word=dog
    echo i=1

    # add the template
    echo "cat << EOF"
    cat template.txt
    echo EOF
) | bash

Thảo luận

  • Các dấu ngoặc đơn mở một lớp vỏ phụ, mục đích của nó là nhóm tất cả các đầu ra được tạo ra
  • Trong lớp vỏ phụ, chúng ta tạo ra tất cả các khai báo biến
  • Cũng trong shell phụ, chúng ta tạo catlệnh với HEREDOC
  • Cuối cùng, chúng tôi cung cấp đầu ra vỏ phụ để bash và tạo ra đầu ra mong muốn
  • Nếu bạn muốn chuyển hướng đầu ra này thành một tệp, hãy thay thế dòng cuối cùng bằng:

    ) | bash > output.txt

3

Trang này mô tả một câu trả lời với awk

awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < input.txt > output.txt

Điều này giữ cho tất cả các trích dẫn nguyên vẹn. Tuyệt quá!
Pepster

3

Trường hợp hoàn hảo cho shtpl . (dự án của tôi, vì vậy nó không được sử dụng rộng rãi và thiếu tài liệu. Nhưng đây là giải pháp mà nó cung cấp bằng mọi cách. Có thể bạn muốn thử nghiệm nó.)

Chỉ cần thực hiện:

$ i=1 word=dog sh -c "$( shtpl template.txt )"

Kết quả là:

the number is 1
the word is dog

Chúc vui vẻ.


1
Nếu nó tào lao, dù sao nó cũng bị hạ cấp. Và tôi ổn với điều đó. Nhưng ok, quan điểm, rằng nó không rõ ràng, đó thực sự là dự án của tôi. Đi để làm cho nó rõ hơn trong tương lai. Cảm ơn bạn dù sao cho bình luận của bạn và thời gian của bạn.
zstegi

Tôi muốn thêm, rằng tôi thực sự đã tìm kiếm các usecase ngày hôm qua, trong đó shtpl sẽ là một giải pháp hoàn hảo. Vâng, tôi đã chán ...
zstegi

3
# Usage: template your_file.conf.template > your_file.conf
template() {
        local IFS line
        while IFS=$'\n\r' read -r line ; do
                line=${line//\\/\\\\}         # escape backslashes
                line=${line//\"/\\\"}         # escape "
                line=${line//\`/\\\`}         # escape `
                line=${line//\$/\\\$}         # escape $
                line=${line//\\\${/\${}       # de-escape ${         - allows variable substitution: ${var} ${var:-default_value} etc
                # to allow arithmetic expansion or command substitution uncomment one of following lines:
#               line=${line//\\\$\(/\$\(}     # de-escape $( and $(( - allows $(( 1 + 2 )) or $( command ) - UNSECURE
#               line=${line//\\\$\(\(/\$\(\(} # de-escape $((        - allows $(( 1 + 2 ))
                eval "echo \"${line}\"";
        done < "$1"
}

Đây là chức năng bash thuần có thể điều chỉnh theo ý thích của bạn, được sử dụng trong sản xuất và không nên phá vỡ bất kỳ đầu vào nào. Nếu nó vỡ - hãy cho tôi biết.



0

Đây là một hàm bash bảo toàn khoảng trắng:

# Render a file in bash, i.e. expand environment variables. Preserves whitespace.
function render_file () {
    while IFS='' read line; do
        eval echo \""${line}"\"
    done < "${1}"
}

0

Đây là perltập lệnh được sửa đổi dựa trên một số câu trả lời khác:

perl -pe 's/([^\\]|^)\$\{([a-zA-Z_][a-zA-Z_0-9]*)\}/$1.$ENV{$2}/eg' -i template

Các tính năng (dựa trên nhu cầu của tôi, nhưng phải dễ sửa đổi):

  • Bỏ qua các mở rộng tham số đã thoát (ví dụ \ $ {VAR}).
  • Hỗ trợ mở rộng tham số có dạng $ {VAR}, nhưng không phải $ VAR.
  • Thay thế $ {VAR} bằng một chuỗi trống nếu không có VAR envar.
  • Chỉ hỗ trợ các ký tự az, AZ, 0-9 và gạch dưới trong tên (không bao gồm các chữ số ở vị trí đầu tiên).

0

Xem tập lệnh python thay thế biến đơn giản tại đây: https://github.com/jeckep/vsubst

Nó rất đơn giản để sử dụng:

python subst.py --props secure.properties --src_path ./templates --dst_path ./dist

0

Đóng góp khiêm tốn của tôi cho câu hỏi tuyệt vời này.

tpl() {
    local file=$(cat - | \
                 sed -e 's/\"/\\"/g' \
                     -e "s/'/\\'/g")
    local vars=$(echo ${@} | tr ' ' ';')
    echo "$(sh -c "${vars};echo \"$file\"")"
}

cat tpl.txt | tpl "one=fish" "two=fish"

Điều này về cơ bản hoạt động bằng cách sử dụng lớp con để thực hiện thay thế envar ngoại trừ việc nó không sử dụng eval và nó thoát khỏi dấu ngoặc đơn và dấu ngoặc kép một cách rõ ràng. Nó ghép các biểu thức var thành một dòng duy nhất không có khoảng trắng để không nhầm lẫn shvà sau đó chuyển mẫu tới echo, cho phép shxử lý các thay thế var. Nó bảo tồn các dòng mới, bình luận, v.v ... và bạn có thể thoát \${like_this}nếu bạn không muốn var được diễn giải. ${missing_var}sẽ chỉ được thay thế bằng giá trị trống.

Nhiều câu trả lời khác ở đây rất hay nhưng tôi muốn một cái gì đó rất đơn giản và nó không cần phải xử lý theo mọi nghĩa đen đối với các trường hợp tạo khuôn mà tôi hiện đang có.

Thưởng thức!


0

Để theo dõi câu trả lời của plockc trên trang này, đây là phiên bản phù hợp với dấu gạch ngang, dành cho những bạn đang tìm cách tránh bashism.

eval "cat <<EOF >outputfile
$( cat template.in )
EOF
" 2> /dev/null

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.