Jak podstawiać zmienne powłoki w złożonych plikach tekstowych

89

Mam kilka plików tekstowych, w których wprowadziłem zmienne powłoki (na przykład $ VAR1 lub $ VAR2).

Chciałbym wziąć te pliki (jeden po drugim) i zapisać je w nowych plikach, gdzie wszystkie zmienne zostałyby zastąpione.

Aby to zrobić, użyłem następującego skryptu powłoki (znalezionego w StackOverflow):

while read line
do
    eval echo "$line" >> destination.txt
done < "source.txt"

Działa to bardzo dobrze w przypadku bardzo podstawowych plików.

Jednak w przypadku bardziej złożonych plików polecenie „eval” robi za dużo:

  • Wiersze zaczynające się od „#” są pomijane

  • Analiza plików XML skutkuje mnóstwem błędów

Czy jest lepszy sposób, aby to zrobić? (w skrypcie powłoki ... wiem, że można to łatwo zrobić na przykład za pomocą Anta)

Z poważaniem

Ben
źródło

Odpowiedzi:

206

Patrząc, okazuje się, że w moim systemie jest envsubstpolecenie, które jest częścią pakietu gettext-base.

Więc to ułatwia:

envsubst < "source.txt" > "destination.txt"

Uwaga, jeśli chcesz używać tego samego pliku dla obu, będziesz musiał użyć czegoś podobnego moreutil użytkownika sponge, jak sugeruje Johnny Utahh: envsubst < "source.txt" | sponge "source.txt". (Ponieważ przekierowanie powłoki w przeciwnym razie opróżni plik przed jego odczytaniem).

derobert
źródło
4
Świetny! Ale działa tylko w przypadku zmiennych środowiskowych. Jak sprawić, by działało ze zmiennymi zadeklarowanymi w moim skrypcie .sh?
Ben
12
@Ben: użyj 'export' (przed wywołaniem envsubst) dla każdej zmiennej, której chcesz użyć w envsubst
tlo
9
envsubstjest częścią GNU gettext
Andy
3
@user_mda, gdzie trafiają dane wyjściowe.
derobert
4
Ostrzeżenie: jeśli source.txtzawiera jakiekolwiek $znaki spoza rozwijania zmiennych, envsubstrównież je zamieni i nie ma sposobu na uniknięcie $. Typowe (brzydkie) obejście to export DOLLAR="$".
Kos
67

W odniesieniu do odpowiedzi 2, omawiając envsubst, zapytałeś:

Jak sprawić, by działało ze zmiennymi zadeklarowanymi w moim skrypcie .sh?

Odpowiedź brzmi: po prostu musisz wyeksportować swoje zmienne przed wywołaniem envsubst.

Możesz również ograniczyć ciągi zmiennych, które chcesz zamienić w danych wejściowych, używając envsubst SHELL_FORMATargumentu (unikając niezamierzonego zastąpienia łańcucha w wejściu wspólną wartością zmiennej powłoki - np $HOME.).

Na przykład:

export VAR1='somevalue' VAR2='someothervalue'
MYVARS='$VAR1:$VAR2'

envsubst "$MYVARS" <source.txt >destination.txt

Zastąpi wszystkie wystąpienia $VAR1i $VAR2(i tylko VAR1a VAR2) w source.txtz 'somevalue'i 'someothervalue'odpowiednio.

Michael
źródło
15
Pojedyncze cudzysłowy, 'które są używane do ustawiania, MYVARSsą kluczowe
Mark Lakata
Zauważ, że export, envsubstlub jakiekolwiek polecenie przeplotu może się nie powieść, gdy tekst do zastąpienia jest duży. Odniesienie.
biskup
@ram to dlatego, że pominięcie SHELL_FORMATargumentu (tj. "$MYVARS") powoduje zastąpienie wszystkich eksportowanych zmiennych. Jeśli tego potrzebujesz, nie martw się.
Thiago Figueiro
14

Wiem, że ten temat jest stary, ale mam prostsze działające rozwiązanie bez eksportu zmiennych. Może być onelinerem, ale ja wolę rozdzielać \na końcu linii.

var1='myVar1'\
var2=2\
var3=${var1}\
envsubst '$var1,$var3' < "source.txt" > "destination.txt"

#         ^^^^^^^^^^^    ^^^^^^^^^^     ^^^^^^^^^^^^^^^
# define which to replace   input            output

Zmienne muszą być zdefiniowane w tej samej linii, w jakiej envsubstmają być traktowane jako zmienne środowiskowe.

'$var1,$var3'Jest opcjonalny tylko wymienić te wyszczególnione. Wyobraź sobie plik wejściowy zawierający, ${VARIABLE_USED_BY_JENKINS}którego nie należy zastępować.

inetphantom
źródło
8
  1. Zdefiniuj zmienną ENV
$ export MY_ENV_VAR=congratulation
  1. Utwórz plik szablonu ( in.txt ) z następującą zawartością
$MY_ENV_VAR

Możesz także użyć wszystkich innych zmiennych ENV zdefiniowanych przez Twój system, takich jak (w systemie Linux) $ TERM, $ SHELL, $ HOME ...

  1. Uruchom to polecenie, aby umieścić wszystkie zmienne env w pliku in.txt i zapisać wynik do out.txt
$ envsubst "`printf '${%s} ' $(sh -c "env|cut -d'=' -f1")`" < in.txt > out.txt
  1. Sprawdź zawartość pliku out.txt
$ cat out.txt

i powinieneś zobaczyć „gratulacje”.

Boazeria
źródło
0

Jeśli naprawdę chcesz używać tylko basha (i seda), przejdę przez każdą z twoich zmiennych środowiskowych (zwróconych setw trybie posix) i zbuduję z tego kilka -e 'regex'dla seda, zakończonych przez-e 's/\$[a-zA-Z_][a-zA-Z0-9_]*//g' , a następnie przekażę to wszystko do sed.

Perl wykonałby jednak lepszą robotę, masz dostęp do zmiennych środowiskowych jako tablicę i możesz wykonywać zastępcze pliki wykonywalne, więc każdą zmienną środowiskową dopasowujesz tylko raz.

w00t
źródło
dlaczego ktoś miałby zignorować taką odpowiedź ??? -perl -pi -e 'foreach $key(sort keys %ENV){ s/\$$key/$ENV{$key}/g}' "$main_tf_file"
Yordan Georgiev
0

Właściwie musisz zmienić readna, read -rktóry spowoduje, że będzie ignorował odwrotne ukośniki.

Powinieneś także unikać cytatów i odwrotnych ukośników. Więc

while read -r line; do
  line="${line//\\/\\\\}"
  line="${line//\"/\\\"}"
  line="${line//\`/\\\`}"
  eval echo "\"$line\""
done > destination.txt < source.txt

Wciąż jednak straszny sposób na ekspansję.

w00t
źródło
Rzeczywiście, jest to ryzyko podczas wykonywania „eval” na pliku, który może zawierać zły kod
Ben,
0

Wyeksportuj wszystkie potrzebne zmienne, a następnie użyj onlinera perla

TEXT=$(echo "$TEXT"|perl -wpne 's#\${?(\w+)}?# $ENV{$1} // $& #ge;')

Spowoduje to zastąpienie wszystkich zmiennych ENV obecnych w TEKST wartościami rzeczywistymi. Cytaty również są zachowane :)

Govind Kailas
źródło
0

Jeśli chcesz, aby zmienne env zostały zastąpione w plikach źródłowych, zachowując wszystkie zmienne inne niż env bez zmian, możesz użyć następującego polecenia:

envsubst "$(printf '${%s} ' $(env | sed 's/=.*//'))" < source.txt > destination.txt

W tym miejscu wyjaśniono składnię zastępowania tylko określonych zmiennych . Powyższe polecenie używa podpowłoki do wyświetlenia wszystkich zdefiniowanych zmiennych, a następnie przekazuje je doenvsubst

Więc jeśli istnieje zdefiniowana zmienna env o nazwie $NAME, a Twój source.txtplik wygląda następująco:

Hello $NAME
Your balance is 123 ($USD)

destination.txtBędą:

Hello Arik
Your balance is 123 ($USD)

Zauważ, że $NAMEzostał zastąpiony i $USDpozostawiony nietknięty

Arik
źródło
0

Wywołaj plik binarny Perla, w trybie wyszukiwania i zamiany na wiersz (the -pi), uruchamiając kod Perla (the -e) w pojedynczych cudzysłowach, który iteruje po kluczach specjalnego %ENVskrótu zawierającego wyeksportowane nazwy zmiennych jako klucze i wyeksportowane wartości zmiennych jako wartości kluczy i dla każdej iteracji po prostu zamień ciąg zawierający a na $<<key>>jego <<value>>.

 perl -pi -e 'foreach $key(sort keys %ENV){ s/\$$key/$ENV{$key}/g}' file

Uwaga: dodatkowa obsługa logiki jest wymagana w przypadkach, w których dwie lub więcej zmiennych zaczyna się od tego samego ciągu ...

Yordan Georgiev
źródło
-1

envsubstwygląda na coś, czego chciałem użyć, ale -vopcja trochę mnie zaskoczyła.

Chociaż envsubst < template.txtdziałał dobrze, to samo z opcją -vnie działało:

$ cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.1 (Maipo)
$ envsubst -V
envsubst (GNU gettext-runtime) 0.18.2
Copyright (C) 2003-2007 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Written by Bruno Haible.

Jak pisałem, to nie działało:

$ envsubst -v < template.txt
envsubst: missing arguments
$ cat template.txt | envsubst -v
envsubst: missing arguments

Musiałem to zrobić, aby działało:

TEXT=`cat template.txt`; envsubst -v "$TEXT"

Może to komuś pomoże.

Betlista
źródło