Bash Templating: Jak zbudować pliki konfiguracyjne z szablonów za pomocą Bash?


141

Piszę skrypt, który zautomatyzuje tworzenie plików konfiguracyjnych dla Apache i PHP dla mojego własnego serwera WWW. Nie chcę używać żadnych GUI, takich jak CPanel czy ISPConfig.

Mam kilka szablonów plików konfiguracyjnych Apache i PHP. Skrypt Bash musi czytać szablony, dokonywać podstawiania zmiennych i umieszczać przeanalizowane szablony w jakimś folderze. Jaki jest najlepszy sposób, aby to zrobić? Mogę wymyślić kilka sposobów. Który z nich jest najlepszy, czy może są na to lepsze sposoby? Chcę to zrobić w czystym Bashu (na przykład w PHP jest to łatwe)

1) Jak zamienić symbole zastępcze $ {} w pliku tekstowym?

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, jak przekierować tutaj wyjście do pliku zewnętrznego? Czy muszę uciec przed czymś, jeśli zmienne zawierają, powiedzmy, cudzysłowy?

2) Użycie cat & sed do zastąpienia każdej zmiennej jej wartością:

Podany plik template.txt:

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

Komenda:

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

Wydaje mi się to złe ze względu na konieczność ucieczki przed wieloma różnymi symbolami i przy wielu zmiennych linia będzie zbyt długa.

Czy przychodzi Ci do głowy jakieś inne eleganckie i bezpieczne rozwiązanie?


Odpowiedzi:


64

Możesz użyć tego:

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

aby zamienić wszystkie ${...}łańcuchy na odpowiednie zmienne środowiskowe (nie zapomnij wyeksportować ich przed uruchomieniem tego skryptu).

W przypadku czystego basha powinno to działać (zakładając, że zmienne nie zawierają łańcuchów $ {...}):

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

. Rozwiązanie, które nie zawiesza się, jeśli RHS odwołuje się do zmiennej, która odwołuje się do siebie:

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

OSTRZEŻENIE : Nie znam sposobu na poprawną obsługę danych wejściowych z wartościami NUL w bashu lub zachowanie liczby końcowych znaków nowej linii. Ostatni wariant jest przedstawiony tak, jak jest, ponieważ powłoki „uwielbiają” wejścia binarne:

  1. read zinterpretuje ukośniki odwrotne.
  2. read -r nie zinterpretuje odwrotnych ukośników, ale nadal usunie ostatnią linię, jeśli nie kończy się nową linią.
  3. "$(…)"obetnie jak wielu nowych linii wahaczami wzdłużnymi, ponieważ nie występują, więc kończy się ; echo -n ai stosowanie echo -n "${line:0:-1}": to spada ostatniego znaku (co jest a) i zachowuje jak wielu nowych linii wahaczami wzdłużnymi, jak było na początku (nie tym).

3
Chciałbym zmienić [^}]się [A-Za-Z_][A-Za-z0-9_]w wersji bash, aby powłoka z wykraczając poza ścisłym podstawienia (np gdyby próbowali procesu ${some_unused_var-$(rm -rf $HOME)}).
Chris Johnsen

2
@FractalizeR możesz chcieć zmienić $&w rozwiązaniu perla na "": najpierw pozostawia ${...}nietknięte, jeśli nie uda się go zastąpić, po drugie zastępuje go pustym ciągiem.
ZyX

5
UWAGA: Najwyraźniej nastąpiła zmiana z bash 3.1 na 3.2 (i nowsze), w której pojedyncze cudzysłowy wokół wyrażenia regularnego - traktują zawartość wyrażenia regularnego jako literał ciągu. Zatem powyższe wyrażenie regularne powinno wyglądać następująco ... (\ $ \ {[a-zA-Z _] [a-zA-Z_0-9] * \}) stackoverflow.com/questions/304864/…
Blue Waters,

2
Aby whilepętla przeczytała ostatnią linię, nawet jeśli nie jest zakończona nową linią, użyj while read -r line || [[ -n $line ]]; do. Dodatkowo, twoje readpolecenie usuwa początkowe i końcowe spacje z każdego wiersza; aby tego uniknąć, użyjwhile IFS= read -r line || [[ -n $line ]]; do
mklement0

2
Wystarczy zwrócić uwagę na ograniczenie dla tych, którzy szukają kompleksowego rozwiązania: te w inny sposób przydatne rozwiązania nie pozwalają na selektywną ochronę odwołań do zmiennych przed rozszerzaniem (na przykład poprzez \ ich unikanie).
mklement0

146

Próbować envsubst

FOO=foo
BAR=bar
export FOO BAR

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

12
Tylko w celach informacyjnych, envsubstnie jest wymagane przy używaniu heredoc, ponieważ bash traktuje heredoc jako dosłowny ciąg w cudzysłowie i interpoluje już w nim zmienne. Jest to jednak doskonały wybór, gdy chcesz odczytać szablon z innego pliku. Dobry zamiennik znacznie bardziej uciążliwych m4.
beporter

2
Byłem bardzo mile zaskoczony, gdy dowiedziałem się o tym poleceniu. Próbowałem ręcznie łączyć funkcjonalność envsubst z zerowym skutkiem. Dzięki, yottatsa!
Tim Stewart,

4
Uwaga: envsubstto narzędzie GNU gettext, które w rzeczywistości nie jest aż tak niezawodne (ponieważ gettext jest przeznaczony do lokalizowania ludzkich wiadomości). Co najważniejsze, nie rozpoznaje podstawień $ {VAR} ze znakami ucieczki odwrotnym ukośnikiem (więc nie możesz mieć szablonu, który używa podstawień $ VAR w czasie wykonywania, jak skrypt powłoki lub plik konfiguracyjny Nginx). Zobacz moją odpowiedź na rozwiązanie, które obsługuje ucieczki z ukośnikiem odwrotnym.
Stuart P. Bentley

4
@beporter W tym przypadku, jeśli z jakiegoś powodu chciałbyś przekazać ten szablon do envsubst, powinieneś użyć <<"EOF", który nie interpoluje zmiennych (terminatory w cudzysłowach są jak pojedyncze cudzysłowy heredoców).
Stuart P. Bentley

2
Użyłem go tak: cat template.txt | envsubst
eigenfield

52

envsubst był dla mnie nowy. Fantastyczny.

Dla przypomnienia, użycie heredoc to świetny sposób na utworzenie szablonu pliku 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
wolę to lepiej niż envsubstbo to uratowało mnie od dodatkowego apt-get install gettext-basew moim pliku Dockerfile
eigenfield

Powłoka jako skrypt podobny do szablonu, jednak bez żadnej instalacji zewnętrznej biblioteki ani stresu związanego z radzeniem sobie z trudnymi wyrażeniami.
千 木 郷

38

Zgadzam się z użyciem seda: jest to najlepsze narzędzie do wyszukiwania / zamiany. Oto moje podejście:

$ 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
Wymaga to pliku tymczasowego do zastąpienia łańcucha, prawda? Czy można to zrobić bez plików tymczasowych?
Vladislav Rastrusny

@FractalizeR: Niektóre wersje seda mają -iopcję (edytuj pliki w miejscu) podobną do opcji perla . Sprawdź stronę podręcznika swojego seda .
Chris Johnsen

@FractalizeR Tak, sed -i zastąpi inline. Jeśli nie przeszkadza Ci Tcl (inny język skryptowy), zajrzyj do tego wątku: stackoverflow.com/questions/2818130/…
Hai Vu

Utworzyłem plik replace.sed z plików właściwości z następującym poleceniem sed: sed -e 's / ^ / s \ / $ {/ g' -e 's / = /} \ // g' -e 's / $ / \ // g 'the.properties> replace.sed
Jaap D

Kod @hai vu tworzy program seda i przekazuje go za pomocą flagi -f seda. Jeśli chcesz, możesz zamiast tego przekazać każdą linię programu seda do seda używając flag -e. FWIW Podoba mi się pomysł używania seda do tworzenia szablonów.
Andrew Allbright,

23

Myślę, że eval działa naprawdę dobrze. Obsługuje szablony z podziałami linii, białymi znakami i wszelkiego rodzaju funkcjami bash. Jeśli oczywiście masz pełną kontrolę nad samymi szablonami:

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

Oczywiście tej metody należy używać ostrożnie, ponieważ eval może wykonać dowolny kod. Uruchomienie tego jako root nie wchodzi w rachubę. Cytaty w szablonie należy uciec, w przeciwnym razie zostaną zjedzone eval.

Można również użyć tu dokumenty, jeśli wolisz cat, abyecho

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

@plockc dostarczyło rozwiązanie, które pozwala uniknąć problemu z ucieczką cytatów basha:

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

Edycja: Usunięto część dotyczącą uruchamiania tego jako root za pomocą sudo ...

Edycja: Dodano komentarz o tym, jak należy uciekać przed cytatami, dodano rozwiązanie plockc do miksu!


To usuwa cudzysłowy, które masz w szablonie i nie zastępuje pojedynczych cudzysłowów, więc w zależności od formatu szablonu może prowadzić do drobnych błędów. Prawdopodobnie ma to jednak zastosowanie do każdej metody tworzenia szablonów opartej na Bash.
Alex B

Szablony oparte na IMHO Bash to szaleństwo, ponieważ musisz być programistą bash, aby zrozumieć, co robi Twój szablon! Ale dzięki za komentarz!
mogsie

@AlexB: To podejście spowoduje zastąpienie pojedynczych cudzysłowów, ponieważ są one po prostu znakami literału wewnątrz otaczającego ciągu w cudzysłowie, a nie ogranicznikami ciągu, gdy polecenia evaled echo / catprzetwarza je; spróbuj eval "echo \"'\$HOME'\"".
mklement0

22

Mam rozwiązanie bash takie jak mogsie ale z heredoc zamiast herestring abyś mógł uniknąć unikania podwójnych cudzysłowów

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

4
To rozwiązanie obsługuje rozszerzanie parametrów Bash w szablonie. Moje ulubione to wymagane parametry z ${param:?}tekstem zagnieżdżonym wokół parametrów opcjonalnych. Przykład: ${DELAY:+<delay>$DELAY</delay>}rozwija się do niczego, gdy DELAY jest nieokreślona i <delay> 17 </delay>, gdy DELAY = 17.
Eric Bolinger

2
O! Separator EOF może używać dynamicznego ciągu, takiego jak PID _EOF_$$.
Eric Bolinger

1
@ mklement0 Obejściem końcowych znaków nowej linii jest użycie pewnych interpretacji, takich jak np. pusta zmienna $trailing_newline, lub użycie$NL5 i upewnienie się, że zostanie rozwinięta jako 5 nowych linii.
xebeche

@xebeche: Tak, umieszczenie tego, co sugerujesz, na samym końcu wewnątrztemplate.txt , działałoby, aby zachować końcowe znaki nowej linii.
mklement0

1
Eleganckie rozwiązanie, ale pamiętaj, że podstawianie poleceń usunie wszystkie końcowe znaki nowej linii z pliku wejściowego, chociaż zazwyczaj nie będzie to problemem. Kolejny skrajny przypadek: z powodu użycia eval, if template.txtzawiera EOFw swoim własnym wierszu, przedwcześnie zakończy działanie here-doc, a tym samym przerwie polecenie. (Końcówka kapelusza do @xebeche).
mklement0

18

Edycja 6 stycznia 2017 r

Musiałem zachować podwójne cudzysłowy w moim pliku konfiguracyjnym, więc podwójne unikanie podwójnych cudzysłowów za pomocą sed pomaga:

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

Nie mogę myśleć o zatrzymywaniu nowych linii końcowych, ale puste linie pomiędzy nimi są zachowywane.


Chociaż to stary temat, IMO znalazłem bardziej eleganckie rozwiązanie tutaj: 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

Wszystkie kredyty dla Grégory'ego Pakosza .


Spowoduje to usunięcie podwójnych cudzysłowów z danych wejściowych i, jeśli w pliku wejściowym znajduje się wiele końcowych znaków nowej linii, zastąpi je pojedynczym.
mklement0

2
Potrzebowałem dwóch ukośników mniej, żeby to zadziałało, tj. eval "echo \"$(sed 's/\"/\\"/g' $1)\""
David Bau

Niestety, takie podejście nie pozwala na tworzenie szablonów plików php (zawierają $variables).
IStranger

10

Jeśli chcesz używać szablonów Jinja2 , zobacz ten projekt: j2cli .

To wspiera:

  • Szablony z plików JSON, INI, YAML i strumieni wejściowych
  • Tworzenie szablonów ze zmiennych środowiskowych

10

Zamiast wymyślać koło na nowo, idź z envsubst Może być używany w prawie każdym scenariuszu, na przykład przy tworzeniu plików konfiguracyjnych ze zmiennych środowiskowych w kontenerach docker.

Jeśli na komputerze Mac upewnij się, że masz homebrew, połącz go z 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

Teraz po prostu użyj:

# 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

ta sekwencja wywołań envsubstfaktycznie działa.
Alexander Oh

Dla nikogo szuka, envsubstnie działa na MacOS, trzeba by zainstalować go za pomocą homebrew: brew install gettext.
Matt,

9

Zrobiłbym to w ten sposób, prawdopodobnie mniej wydajne, ale łatwiejsze do odczytania / utrzymania.

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

11
Możesz to zrobić bez czytania linijka po linijce i tylko z jednym wywołaniem seda:sed -e 's/VARONE/NEWVALA/g' -e 's/VARTWO/NEWVALB/g' -e 's/VARTHR/NEWVALC/g' < $TEMPLATE > $OUTPUT
Brandon Bloom,

9

Dłuższa, ale solidniejsza wersja zaakceptowanej odpowiedzi:

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

Spowoduje to rozwinięcie wszystkich wystąpień $VAR lub ${VAR} do ich wartości środowiskowych (lub, jeśli są one niezdefiniowane, pustego ciągu).

Prawidłowo wymyka się odwrotnym ukośnikiem i akceptuje znak $ przed odwrotnym ukośnikiem, aby uniemożliwić podstawianie (w przeciwieństwie do envsubst, który, jak się okazuje, nie robi tego ).

Jeśli więc Twoje środowisko jest:

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

a twój szablon to:

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

wynik byłby:

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

Jeśli chcesz usunąć tylko ukośniki odwrotne przed $ (możesz napisać „C: \ Windows \ System32” w szablonie bez zmian), użyj tej nieznacznie zmodyfikowanej wersji:

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

6

Oto kolejne czyste rozwiązanie bash:

  • używa heredoc, więc:
    • złożoność nie wzrasta z powodu dodatkowej wymaganej składni
    • szablon może zawierać kod bash
      • pozwala to również na prawidłowe wcięcie elementów. Zobacz poniżej.
  • nie używa eval, więc:
    • żadnych problemów z renderowaniem końcowych pustych linii
    • żadnych problemów z cudzysłowami w szablonie

$ cat code

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

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

cat_template template | LISTING="$LISTING" bash

$ cat template (z końcowymi znakami nowej linii i podwójnymi cudzysłowami)

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

wynik

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

5

Biorąc odpowiedź z ZyX przy użyciu czystego basha, ale z nowym dopasowaniem wyrażeń regularnych i pośrednim zastępowaniem parametrów, wygląda to następująco:

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

Jeśli używanie Perla jest opcją i jesteś zadowolony z opierania rozszerzeń wyłącznie na zmiennych środowiskowych (w przeciwieństwie do wszystkich zmiennych powłoki ), rozważ solidną odpowiedź Stuarta P. Bentleya .

Ta odpowiedź ma na celu dostarczenie rozwiązania opartego tylko na bash, które - pomimo użycia eval- powinno być bezpieczne w użyciu .

Te cele są następujące:

  • Obsługa rozszerzania zarówno referencji, jak ${name}i $namezmiennych
  • Zapobiegaj wszystkim innym rozszerzeniom:
    • podstawienia poleceń ( $(...)i starsza składnia `...`)
    • podstawienia arytmetyczne ( $((...))i starsza składnia $[...]).
  • Zezwalaj na selektywne pomijanie rozwijania zmiennych, poprzedzając je przedrostkiem \( \${name}).
  • Zachowaj specjalne znaki. w danych wejściowych, zwłaszcza "i \przypadkach.
  • Zezwalaj na wprowadzanie danych za pośrednictwem argumentów lub standardowego wejścia.

FunkcjaexpandVars() :

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

Przykłady:

$ 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
  • Ze względu na wydajność funkcja odczytuje dane wejściowe stdin jednocześnie do pamięci, ale łatwo jest dostosować funkcję do podejścia linia po linii.
  • Obsługuje również inne niż podstawowe rozwinięcia zmiennych, takie jak ${HOME:0:10}, o ile nie zawierają one osadzonego polecenia lub podstawień arytmetycznych, takich jak${HOME:0:$(echo 10)}
    • Takie osadzone podstawienia faktycznie ŁAMĄ funkcję (ponieważ wszystkie $(i `instancje są ślepo uciekane).
    • Podobnie, źle sformułowane odwołania do zmiennych, takie jak ${HOME(brak zamknięcia }) BREAK funkcja.
  • Ze względu na obsługę przez bash ciągów w podwójnych cudzysłowach, ukośniki odwrotne są obsługiwane w następujący sposób:
    • \$name zapobiega ekspansji.
    • Singiel, \po którym nie następuje, $jest zachowywany bez zmian.
    • Jeśli chcesz reprezentować wiele sąsiednich \ instancji, musisz je podwoić ; na przykład:
      • \\-> \- to samo co just\
      • \\\\ -> \\
    • Wejście nie musi zawierać następujące elementy (rzadko stosowane) znaków, które są wykorzystywane do celów wewnętrznych: 0x1, 0x2, 0x3.
  • Istnieje w dużej mierze hipotetyczna obawa, że ​​jeśli bash wprowadzi nową składnię rozszerzenia, ta funkcja może nie zapobiec takim rozszerzeniom - zobacz poniżej rozwiązanie, które nie używa eval.

Jeśli szukasz bardziej restrykcyjnego rozwiązania, które obsługuje tylko${name} rozszerzenia - tj. Z obowiązkowymi nawiasami klamrowymi, ignorując $nameodniesienia - zobacz moją odpowiedź .


Oto ulepszona wersja evalrozwiązania opartego tylko na bash, wolnego od zaakceptowanej odpowiedzi :

Ulepszenia to:

  • Obsługa rozszerzania odwołań zarówno ${name}do $namezmiennych, jak i zmiennych.
  • Obsługa \odwołań do zmiennych, które nie powinny być rozwijane.
  • W przeciwieństwie do evalpowyższego rozwiązania opartego na
    • rozszerzenia inne niż podstawowe są ignorowane
    • źle sformułowane odwołania do zmiennych są ignorowane (nie powodują uszkodzenia skryptu)
 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"

4

Oto inne rozwiązanie: wygeneruj skrypt bash ze wszystkimi zmiennymi i zawartością pliku szablonu, skrypt ten wyglądałby tak:

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

EOF                

Gdybyśmy wprowadzili ten skrypt do basha, dałby on pożądane wyjście:

the number is 1
the word is dog

Oto jak wygenerować ten skrypt i przesłać go do basha:

(
    # Variables
    echo word=dog
    echo i=1

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

Dyskusja

  • Nawiasy otwierają podpowłokę, której celem jest zgrupowanie wszystkich wygenerowanych wyników
  • W powłoce podrzędnej generujemy wszystkie deklaracje zmiennych
  • Również w powłoce dodatkowej generujemy plik cat polecenie za pomocą HEREDOC
  • Na koniec dostarczamy wyjście podpowłoki do bash i tworzymy żądane wyjście
  • Jeśli chcesz przekierować to wyjście do pliku, zamień ostatnią linię na:

    ) | bash > output.txt
    

3

Ta strona opisuje odpowiedź z awk

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

Dzięki temu wszystkie cytaty pozostają nienaruszone. Świetny!
Pepster

3

Idealna obudowa do shtpl . (mój projekt, więc nie jest powszechnie używany i brakuje mu dokumentacji. Ale oto rozwiązanie, które oferuje. Może zechcesz go przetestować.)

Po prostu wykonaj:

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

Wynik to:

the number is 1
the word is dog

Baw się dobrze.


1
Jeśli to bzdury, i tak jest dyskutowane. I nie przeszkadza mi to. Ale ok, zwróć uwagę, że nie jest to wyraźnie widoczne, że to faktycznie mój projekt. Zamierzam uczynić go bardziej widocznym w przyszłości. Dziękuję za komentarz i poświęcony czas.
zstegi

Dodam, że naprawdę wczoraj szukałem przypadków użycia, w których shtpl byłoby idealnym rozwiązaniem. Tak, nudziłem się ...
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"
}

Jest to czysta funkcja bash, którą można dostosować do własnych upodobań, używana w produkcji i nie powinna się zepsuć na żadnym wejściu. Jeśli się zepsuje - daj mi znać.



0

Oto funkcja bash, która zachowuje białe znaki:

# 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

Oto zmodyfikowany perlskrypt oparty na kilku innych odpowiedziach:

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

Funkcje (w zależności od moich potrzeb, ale powinny być łatwe do modyfikacji):

  • Pomija rozszerzenia parametrów ze zmianą znaczenia (np. \ $ {VAR}).
  • Obsługuje rozszerzanie parametrów w postaci $ {VAR}, ale nie $ VAR.
  • Zastępuje $ {VAR} pustym ciągiem, jeśli nie ma zmiennej zmiennej VAR.
  • Obsługuje tylko znaki az, AZ, 0-9 i podkreślenia w nazwie (z wyłączeniem cyfr na pierwszej pozycji).


0

Aby kontynuować odpowiedź plockca na tej stronie, oto wersja odpowiednia dla myślnika, dla tych z Was, którzy chcą uniknąć bashizmów.

eval "cat <<EOF >outputfile
$( cat template.in )
EOF
" 2> /dev/null
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.