Templat Bash: Cara membuat file konfigurasi dari templat dengan Bash?


134

Saya sedang menulis skrip untuk secara otomatis membuat file konfigurasi untuk Apache dan PHP untuk server web saya sendiri. Saya tidak ingin menggunakan GUI seperti CPanel atau ISPConfig.

Saya memiliki beberapa template file konfigurasi Apache dan PHP. Script Bash perlu membaca templat, membuat subtitle variabel dan templat keluaran diuraikan ke dalam beberapa folder. Apa cara terbaik untuk melakukannya? Saya dapat memikirkan beberapa cara. Mana yang terbaik atau mungkin ada beberapa cara yang lebih baik untuk melakukan itu? Saya ingin melakukannya dalam Bash murni (mudah dalam PHP misalnya)

1) Bagaimana cara mengganti $ {} placeholder dalam file teks?

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, bagaimana cara mengarahkan output ke file eksternal di sini? Apakah saya perlu melarikan diri sesuatu jika variabel berisi, katakanlah, tanda kutip?

2) Menggunakan cat & sed untuk mengganti setiap variabel dengan nilainya:

Template.txt yang diberikan:

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

Perintah:

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

Tampak buruk bagi saya karena kebutuhan untuk melarikan diri banyak simbol yang berbeda dan dengan banyak variabel garisnya akan terlalu panjang.

Bisakah Anda memikirkan beberapa solusi elegan dan aman lainnya?


Jawaban:


61

Anda bisa menggunakan ini:

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

untuk mengganti semua ${...}string dengan variabel lingkungan yang sesuai (jangan lupa untuk mengekspornya sebelum menjalankan skrip ini).

Untuk bash murni, ini harus berfungsi (dengan asumsi variabel tidak mengandung string $ {...}):

#!/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

. Solusi yang tidak hang jika RHS mereferensikan beberapa variabel yang mereferensikan dirinya:

#!/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}"

PERINGATAN : Saya tidak tahu cara menangani input dengan benar dengan NUL di bash atau mempertahankan jumlah trailing newlines. Varian terakhir disajikan sebagaimana adanya karena input biner dari shell: "love":

  1. read akan menafsirkan garis miring terbalik.
  2. read -r tidak akan mengartikan garis miring terbalik, tetapi masih akan menghapus baris terakhir jika tidak diakhiri dengan baris baru.
  3. "$(…)"akan menghapus sebanyak mungkin baris baru yang ada, jadi saya akhiri dengan ; echo -n adan menggunakan echo -n "${line:0:-1}": ini akan menghapus karakter terakhir (yang a) dan mempertahankan baris baru sebanyak yang ada di input (termasuk no).

3
Saya akan mengubah [^}]ke [A-Za-Z_][A-Za-z0-9_]dalam versi bash untuk mencegah shell melampaui substitusi yang ketat (misalnya jika mencoba untuk memproses ${some_unused_var-$(rm -rf $HOME)}).
Chris Johnsen

2
@FractalizeR Anda mungkin ingin mengubah $&dalam solusi perl untuk "": pertama ${...}tidak tersentuh jika gagal untuk menggantikan, kedua menggantikannya dengan string kosong.
ZyX

5
CATATAN: Rupanya ada perubahan dari bash 3.1 ke 3.2 (dan lebih tinggi) di mana tanda kutip tunggal di sekitar regex - perlakukan konten regex sebagai string literal. Jadi regex di atas harus ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/…
Blue Waters

2
Untuk membuat whileloop membaca baris terakhir bahkan jika itu tidak diakhiri oleh baris baru, gunakan while read -r line || [[ -n $line ]]; do. Selain itu, readcommand strip Anda memimpin dan mengikuti spasi putih dari setiap baris; untuk menghindarinya, gunakanwhile IFS= read -r line || [[ -n $line ]]; do
mklement0

2
Hanya untuk mencatat kendala bagi mereka yang mencari solusi komprehensif: Solusi yang berguna ini tidak memungkinkan Anda untuk secara selektif melindungi referensi variabel dari ekspansi (seperti dengan \ -menghilangkan mereka).
mklement0

138

Mencoba envsubst

FOO=foo
BAR=bar
export FOO BAR

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

11
Hanya untuk referensi, envsubsttidak diperlukan ketika menggunakan heredoc karena bash memperlakukan heredoc sebagai string yang dikutip ganda secara literal dan menginterpolasikan variabel di dalamnya. Ini adalah pilihan yang bagus ketika Anda ingin membaca templat dari file lain. Pengganti yang baik untuk yang jauh lebih rumit m4.
beporter

2
Saya sangat terkejut mengetahui tentang perintah ini. Saya mencoba untuk memperbaiki fungsi envsubst secara manual dengan nol keberhasilan. Yottatsa terima kasih!
Tim Stewart

4
Catatan: envsubstadalah utilitas gettext GNU, dan sebenarnya tidak terlalu kuat (karena gettext dimaksudkan untuk melokalisasi pesan manusia). Yang paling penting, ia tidak mengenali penggantian $ {VAR} backslash-lolos (jadi Anda tidak dapat memiliki templat yang menggunakan substitusi $ VAR saat runtime, seperti skrip shell atau file conf Nginx). Lihat jawaban saya untuk solusi yang menangani lolos backslash.
Stuart P. Bentley

4
@beporter Dalam hal ini, jika Anda ingin meneruskan templat ini ke envsubst karena beberapa alasan, Anda ingin menggunakan <<"EOF", yang tidak menginterpolasi variabel (terminator yang dikutip seperti tanda kutip tunggal heredocs).
Stuart P. Bentley

2
Saya menggunakannya seperti: cat template.txt | envsubst
truthadjustr

47

envsubst baru bagi saya. Fantastis.

Sebagai catatan, menggunakan heredoc adalah cara yang bagus untuk membuat templat file 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
saya lebih suka ini lebih baik daripada envsubstkarena itu menyelamatkan saya dari tambahan apt-get install gettext-basedi Dockerfile saya
truthadjustr

Shell sebagai skrip mirip Templat namun tanpa instalasi pustaka eksternal atau tekanan dari mengatasi ekspresi yang rumit.
千 木 郷

35

Saya setuju dengan menggunakan sed: ini adalah alat terbaik untuk mencari / mengganti. Inilah pendekatan saya:

$ 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
Ini membutuhkan file sementara untuk string pengganti, bukan? Apakah ada cara untuk melakukan itu tanpa file sementara?
Vladislav Rastrusny

@FractalizeR: Beberapa versi sed memiliki -iopsi (edit file di tempat) yang mirip dengan opsi perl . Periksa halaman manual untuk sed Anda .
Chris Johnsen

@FractalizeR Ya, sed -i akan menggantikan inline. Jika Anda merasa nyaman dengan Tcl (bahasa skrip lain), maka periksa utas ini: stackoverflow.com/questions/2818130/…
Hai Vu

Saya membuat replace.sed dari propertyfile dengan perintah sed berikut: sed -e 's / ^ / s \ / $ {/ g' -e 's / = /} \ // g' -e 's / $ / \ // g the.properties> replace.sed
Jaap D

Kode @hai vu membuat program sed dan meneruskan program itu dalam menggunakan flag sed -f. Jika Anda mau, Anda bisa meneruskan di setiap baris program sed ke sed menggunakan flag -e. FWIW Saya suka ide menggunakan sed untuk templating.
Andrew Allbright

23

Saya pikir eval bekerja dengan sangat baik. Ini menangani template dengan linebreak, spasi putih, dan segala macam hal bash. Jika Anda memiliki kontrol penuh atas template itu sendiri tentu saja:

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

Metode ini harus digunakan dengan hati-hati, tentu saja, karena eval dapat mengeksekusi kode arbitrer. Menjalankan ini sebagai root cukup banyak dari pertanyaan. Kutipan dalam templat harus diloloskan, jika tidak maka akan dimakan oleh eval.

Anda juga dapat menggunakan disini dokumen jika Anda lebih memilih catuntukecho

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

@plockc memprovokasi solusi yang menghindari masalah bash quote escape:

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

Sunting: Bagian yang dihapus tentang menjalankan ini sebagai root menggunakan ...

Sunting: Menambahkan komentar tentang bagaimana kutipan perlu diloloskan, menambahkan solusi plockc ke dalam campuran!


Strip ini mengutip yang Anda miliki di template Anda, dan tidak akan menggantikan di dalam tanda kutip tunggal, jadi tergantung pada format template Anda, dapat menyebabkan bug halus. Ini mungkin berlaku untuk metode templating berbasis Bash, meskipun.
Alex B

Templat berbasis IMHO Bash adalah kegilaan, karena Anda harus menjadi pemrogram bash untuk memahami apa yang dilakukan templat Anda! Tapi terima kasih atas komentarnya!
mogsie

@AlexB: Pendekatan ini akan menggantikan antara tanda kutip tunggal, karena mereka hanya karakter literal di dalam string yang dikutip ganda daripada pembatas string ketika evaled echo / catperintah memprosesnya; coba eval "echo \"'\$HOME'\"".
mklement0

21

Saya punya solusi bash seperti mogsie tetapi dengan heredoc dan bukan herestring untuk memungkinkan Anda menghindari melarikan diri dari tanda kutip ganda

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

4
Solusi ini mendukung perluasan parameter Bash dalam templat. Favorit saya adalah parameter yang diperlukan dengan ${param:?}dan bersarang teks di sekitar parameter opsional. Contoh: tidak ${DELAY:+<delay>$DELAY</delay>}berkembang menjadi apa pun ketika DELAY tidak ditentukan dan <delay> 17 </delay> saat DELAY = 17.
Eric Bolinger

2
Oh! Dan pembatas EOF dapat menggunakan string dinamis, seperti PID _EOF_$$.
Eric Bolinger

1
@ mklement0 Sebuah solusi untuk mengikuti baris baru adalah dengan menggunakan beberapa ekspansi seperti misalnya variabel kosong $trailing_newline, atau gunakan $NL5dan pastikan itu diperluas sebagai 5 baris baru.
xebeche

@ xebeche: Ya, menempatkan apa yang Anda sarankan di bagian paling dalamtemplate.txt akan berfungsi untuk menjaga jalur baru.
mklement0

1
Solusi yang elegan, tetapi perhatikan bahwa substitusi perintah akan menghapus semua baris baru dari file input, meskipun itu biasanya tidak akan menjadi masalah. Kasus tepi lain: karena penggunaan eval, jika template.txtberisi EOFpada baris sendiri, itu akan mengakhiri prematur di sini-doc dan dengan demikian melanggar perintah. (Ujung topi ke @ xebeche).
mklement0

18

Edit 6 Jan 2017

Saya perlu menyimpan tanda kutip ganda dalam file konfigurasi saya sehingga lolos tanda kutip ganda dengan sed membantu:

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

Saya tidak bisa berpikir untuk tetap mengikuti garis baru, tetapi garis kosong di antaranya tetap dipertahankan.


Meskipun ini adalah topik lama, IMO saya menemukan solusi yang lebih elegan di sini: 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

Semua kredit untuk Grégory Pakosz .


Ini menghapus tanda kutip ganda dari input dan, jika ada beberapa baris baru di file input, menggantikannya dengan satu.
mklement0

2
Saya membutuhkan dua backslash yang lebih sedikit untuk membuatnya bekerja, yaitu, eval "echo \"$(sed 's/\"/\\"/g' $1)\""
David Bau

Sayangnya, pendekatan ini tidak memungkinkan Anda untuk templat file php (mengandung $variables).
IStranger

10

Alih-alih menciptakan kembali roda, lanjutkan dengan envsubst Dapat digunakan di hampir semua skenario, misalnya membangun file konfigurasi dari variabel lingkungan dalam wadah buruh pelabuhan.

Jika di mac pastikan Anda memiliki homebrew kemudian tautkan dari 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

./configure.sh

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

Sekarang gunakan saja:

# 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

urutan doa ini envsubstbenar - benar berfungsi.
Alex

Untuk orang lain tampak, envsubsttidak bekerja pada MacOS, Anda akan perlu untuk menginstalnya menggunakan homebrew: brew install gettext.
Matt

9

Versi lebih lama dari jawaban yang diterima:

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

Ini memperluas semua instance $VAR atau ${VAR} ke nilai lingkungannya (atau, jika tidak terdefinisi, string kosong).

Itu benar lolos backslash, dan menerima $ backslash-melarikan diri untuk menghambat substitusi (tidak seperti envsubst, yang, ternyata, tidak melakukan ini ).

Jadi, jika lingkungan Anda adalah:

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

dan templat Anda adalah:

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

hasilnya adalah:

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

Jika Anda hanya ingin lepas dari garis miring terbalik sebelum $ (Anda dapat menulis "C: \ Windows \ System32" dalam templat yang tidak berubah), gunakan versi yang sedikit dimodifikasi ini:

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

Saya akan melakukannya dengan cara ini, mungkin kurang efisien, tetapi lebih mudah dibaca / dikelola.

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
Anda dapat melakukan ini tanpa membaca baris demi baris dan hanya dengan satu permintaan:sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Brandon Bloom

8

Jika Anda ingin menggunakan templat Jinja2 , lihat proyek ini: j2cli .

Ini mendukung:

  • Template dari file JSON, INI, YAML dan input stream
  • Templating dari variabel lingkungan

5

Mengambil jawaban dari ZyX menggunakan bash murni tetapi dengan pencocokan regex gaya baru dan substitusi parameter tidak langsung menjadi:

#!/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

Jika menggunakan Perl adalah opsi dan Anda puas dengan mendasarkan ekspansi hanya pada variabel lingkungan (sebagai lawan dari semua variabel shell ), pertimbangkan jawaban kuat Stuart P. Bentley .

Jawaban ini bertujuan untuk memberikan solusi bash-only yang - meskipun digunakan eval- harus aman digunakan .

The tujuan adalah:

  • Mendukung perluasan keduanya ${name}dan$name referensi variabel.
  • Cegah semua ekspansi lainnya:
    • substitusi perintah ( $(...)dan sintaks yang lama`...` )
    • substitusi aritmatika ( $((...))dan sintaksis lama $[...]).
  • Izinkan penindasan selektif ekspansi variabel dengan awalan dengan \(\${name} ).
  • Pertahankan karakter khusus. dalam input, khususnya "dan \contoh.
  • Izinkan input baik melalui argumen atau via stdin.

FungsiexpandVars() :

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' '`(['
}

Contoh:

$ 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
  • Untuk alasan kinerja, fungsi membaca input stdin sekaligus ke dalam memori, tetapi mudah untuk menyesuaikan fungsi dengan pendekatan baris demi baris.
  • Juga mendukung ekspansi variabel non-dasar seperti ${HOME:0:10}, selama tidak mengandung perintah tertanam atau penggantian aritmatika, seperti${HOME:0:$(echo 10)}
    • Substitusi yang disematkan sebenarnya BREAK fungsi (karena semua $(dan `contoh melarikan diri secara buta).
    • Demikian pula, referensi variabel cacat seperti ${HOME(hilang penutupan }) BREAK fungsi.
  • Karena penanganan bash atas string yang dikutip ganda, backslash ditangani sebagai berikut:
    • \$name mencegah ekspansi.
    • Satu \tidak diikuti oleh $dipertahankan seperti apa adanya.
    • Jika Anda ingin mewakili beberapa \ instance yang berdekatan , Anda harus menggandakannya ; misalnya:
      • \\-> \- sama dengan adil\
      • \\\\ -> \\
    • Input tidak harus berisi berikut (jarang digunakan) karakter, yang digunakan untuk keperluan internal: 0x1, 0x2, 0x3.
  • Ada sebagian besar kekhawatiran hipotetis bahwa jika bash harus memperkenalkan sintaksis ekspansi baru, fungsi ini mungkin tidak mencegah ekspansi seperti itu - lihat di bawah untuk solusi yang tidak digunakan eval.

Jika Anda mencari solusi yang lebih ketat yang hanya mendukung ${name}ekspansi - yaitu, dengan kurung kurawal wajib , abaikan $namereferensi - lihat jawaban saya ini.


Ini adalah versi perbaikan dari solusi bash-only, eval-gratis dari jawaban yang diterima :

Perbaikannya adalah:

  • Dukungan untuk perluasan keduanya ${name}dan$name referensi variabel.
  • Mendukung \ -menghapus referensi variabel yang tidak boleh diperluas.
  • Berbeda dengan evalsolusi berbasis di atas,
    • ekspansi non-dasar diabaikan
    • referensi variabel salah diabaikan (tidak merusak skrip)
 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

Inilah solusi bash murni lainnya:

  • itu menggunakan heredoc, jadi:
    • kompleksitas tidak bertambah karena sintaksis tambahan yang diperlukan
    • template dapat menyertakan kode bash
      • itu juga memungkinkan Anda untuk membuat indentasi dengan benar. Lihat di bawah.
  • itu tidak menggunakan eval, jadi:
    • tidak ada masalah dengan rendering dari trailing baris kosong
    • tidak ada masalah dengan kutipan di template

$ cat code

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

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

cat_template template | LISTING="$LISTING" bash

$ cat template (dengan mengikuti baris baru dan tanda kutip ganda)

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

keluaran

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

4

Berikut ini solusi lain: menghasilkan skrip bash dengan semua variabel dan konten file templat, skrip tersebut akan terlihat seperti ini:

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

EOF                

Jika kita memasukkan skrip ini ke bash, itu akan menghasilkan output yang diinginkan:

the number is 1
the word is dog

Inilah cara membuat skrip itu dan memasukkan skrip itu ke bash:

(
    # Variables
    echo word=dog
    echo i=1

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

Diskusi

  • Tanda kurung membuka sub shell, tujuannya adalah untuk mengelompokkan semua output yang dihasilkan
  • Di dalam sub shell, kami menghasilkan semua deklarasi variabel
  • Juga di sub shell, kami membuat catperintah dengan HEREDOC
  • Akhirnya, kami memberi makan output sub shell ke bash dan menghasilkan output yang diinginkan
  • Jika Anda ingin mengarahkan output ini ke file, ganti baris terakhir dengan:

    ) | bash > output.txt

3

Halaman ini menjelaskan jawaban dengan awk

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

Ini menjaga semua kutipan tetap utuh. Bagus!
Pepster

3

Kasing yang sempurna untuk shtpl . (proyek saya, jadi tidak banyak digunakan dan kurang dalam dokumentasi. Tapi di sini adalah solusi yang ditawarkan. Semoga Anda ingin mengujinya.)

Eksekusi saja:

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

Hasilnya adalah:

the number is 1
the word is dog

Selamat bersenang-senang.


1
Jika itu omong kosong, itu tetap diturunkan. Dan saya setuju dengan itu. Tapi ok, poin diambil, bahwa itu tidak terlihat jelas, bahwa itu sebenarnya proyek saya. Akan membuatnya lebih terlihat di masa depan. Bagaimanapun terima kasih atas komentar dan waktu Anda.
zstegi

Saya ingin menambahkan, bahwa saya benar-benar mencari usecases kemarin, di mana shtpl akan menjadi solusi yang sempurna. Ya, saya bosan ...
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"
}

Ini adalah fungsi bash murni yang dapat disesuaikan dengan keinginan Anda, digunakan dalam produksi dan tidak boleh merusak input apa pun. Jika rusak - beri tahu saya.



0

Inilah fungsi bash yang mempertahankan spasi putih:

# 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

Berikut ini perlskrip yang dimodifikasi berdasarkan pada beberapa jawaban lain:

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

Fitur (berdasarkan kebutuhan saya, tetapi harus mudah dimodifikasi):

  • Melompati ekspansi parameter (mis. \ $ {VAR}).
  • Mendukung ekspansi parameter dari bentuk $ {VAR}, tetapi bukan $ VAR.
  • Mengganti $ {VAR} dengan string kosong jika tidak ada VAR envar.
  • Hanya mendukung karakter az, AZ, 0-9, dan garis bawah dalam nama (tidak termasuk digit pada posisi pertama).


0

Kontribusi saya yang sederhana terhadap pertanyaan yang luar biasa ini.

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"

Ini pada dasarnya bekerja dengan menggunakan subkulit untuk melakukan penggantian envar kecuali bahwa ia tidak menggunakan eval dan ia lolos dari kutip tunggal dan ganda secara eksplisit. Ini menggabungkan ekspresi var menjadi satu baris tanpa spasi untuk tidak membingungkan shdan kemudian meneruskan template echo, membiarkan shmenangani penggantian var. Ini mempertahankan baris baru, komentar dll ... dan Anda dapat melarikan diri \${like_this}jika Anda tidak ingin var ditafsirkan.${missing_var}hanya akan diganti dengan nilai kosong.

Banyak jawaban lain di sini sangat bagus tetapi saya menginginkan sesuatu yang sangat sederhana dan tidak perlu menangani setiap kasus yang mungkin untuk kasus templating yang saya miliki saat ini.

Nikmati!


0

Untuk menindaklanjuti jawaban plockc pada halaman ini, berikut adalah versi yang cocok untuk Anda yang ingin menghindari bashism.

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.