Zmuszanie basha do rozwijania zmiennych w łańcuchu ładowanym z pliku


88

Próbuję dowiedzieć się, jak zrobić bash (wymusić?) Rozwinąć zmienne w ciągu (który został załadowany z pliku).

Mam plik o nazwie „coś.txt” z zawartością:

hello $FOO world

Potem biegnę

export FOO=42
echo $(cat something.txt)

to zwraca:

   hello $FOO world

Nie rozszerzał $ FOO, mimo że zmienna była ustawiona. Nie mogę ewaluować ani pozyskiwać pliku - ponieważ spróbuje go wykonać (nie jest wykonywalnym, jak jest - chcę tylko ciąg znaków ze zmiennymi interpolowanymi).

Jakieś pomysły?



Czy chodziło Ci o komentarz dotyczący odpowiedzi poniżej?
Michael Neale

Miałem na myśli komentarz dla ciebie. Więcej niż jedna odpowiedź sugeruje użycie evali ważne jest, aby być świadomym konsekwencji.
Wstrzymano do odwołania.

@DennisWilliamson gotcha - Odpowiadam trochę za późno, ale dobra wskazówka. I oczywiście w ostatnich latach mieliśmy takie rzeczy jak „szok muszelkowy”, więc Twój komentarz przetrwał próbę czasu! czapka napiwków
Michael Neale

Czy to odpowiada na twoje pytanie? Bash expand zmienna w zmiennej
LoganMzz

Odpowiedzi:


111

Natknąłem się na to, co uważam za odpowiedź na to pytanie: envsubstpolecenie.

envsubst < something.txt

Jeśli nie jest jeszcze dostępny w Twojej dystrybucji, znajduje się w

Pakiet GNU gettext.

@Rockallite - Napisałem mały skrypt opakowujący, aby zająć się problemem „\ $”.

(BTW, istnieje "funkcja" envsubst, wyjaśniona na https://unix.stackexchange.com/a/294400/7088 w celu rozwijania tylko niektórych zmiennych wejściowych, ale zgadzam się, że unikanie wyjątków to znacznie więcej wygodna.)

Oto mój skrypt:

#! /bin/bash
      ## -*-Shell-Script-*-
CmdName=${0##*/}
Usage="usage: $CmdName runs envsubst, but allows '\$' to  keep variables from
    being expanded.
  With option   -sl   '\$' keeps the back-slash.
  Default is to replace  '\$' with '$'
"

if [[ $1 = -h ]]  ;then echo -e >&2  "$Usage" ; exit 1 ;fi
if [[ $1 = -sl ]] ;then  sl='\'  ; shift ;fi

sed 's/\\\$/\${EnVsUbDolR}/g' |  EnVsUbDolR=$sl\$  envsubst  "$@"

5
+1. To jedyna odpowiedź, która gwarantuje, że nie będzie zastępować poleceń, usuwać cytatów, przetwarzać argumenty itp.
Josh Kelley

6
Działa jednak tylko dla w pełni wyeksportowanych zmiennych powłoki (w bash). np. S = '$ a $ b'; a = zestaw; eksport b = eksportowany; echo $ S | envsubst; plony: "eksportowane"
gaoithe

1
dzięki - myślę, że po tylu latach powinienem przyjąć tę odpowiedź.
Michael Neale

1
Jednak nie obsługuje $ucieczki znaku dolara ( ), np echo '\$USER' | envsubst. Wyświetli bieżącą nazwę użytkownika z przedrostkiem ukośnika wstecznego, a nie $USER.
Rockallite

3
wydaje się, że to działa na komputerze Mac z potrzebami homebrewbrew link --force gettext
KeepCalmAndCarryOn

25

Wiele odpowiedzi wykorzystuje evali echorodzaj pracy, ale przerywa różne rzeczy, takie jak wiele wierszy, próba ucieczki z meta-znaków powłoki, ucieczki wewnątrz szablonu, które nie mają być rozszerzone przez bash itp.

Miałem ten sam problem i napisałem tę funkcję powłoki, która o ile wiem, obsługuje wszystko poprawnie. To nadal usunie tylko końcowe znaki nowej linii z szablonu, z powodu reguł podstawiania poleceń basha, ale nigdy nie stwierdziłem, że jest to problem, o ile wszystko inne pozostaje nienaruszone.

apply_shell_expansion() {
    declare file="$1"
    declare data=$(< "$file")
    declare delimiter="__apply_shell_expansion_delimiter__"
    declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter"
    eval "$command"
}

Na przykład, możesz go użyć w ten sposób z, parameters.cfgktóry jest tak naprawdę skryptem powłoki, który po prostu ustawia zmienne, i template.txtktóry jest szablonem, który używa tych zmiennych:

. parameters.cfg
printf "%s\n" "$(apply_shell_expansion template.txt)" > result.txt

W praktyce używam tego jako pewnego rodzaju lekkiego systemu szablonów.


4
To jedna z najlepszych sztuczek, jakie do tej pory znalazłem :). W oparciu o twoją odpowiedź bardzo łatwo jest zbudować funkcję, która rozszerza zmienną ciągiem zawierającym odniesienia do innych zmiennych - po prostu zamień $ data na $ 1 w swojej funkcji i gotowe! Uważam, że to najlepsza odpowiedź na pierwotne pytanie, BTW.
galaktyka

Nawet to ma typowe evalpułapki. Spróbuj dodać ; $(eval date)na końcu dowolnej linii w pliku wejściowym, a uruchomi datepolecenie.
anubhava

1
@anubhava Nie jestem pewien, co masz na myśli; jest to właściwie pożądane zachowanie ekspansji w tym przypadku. Innymi słowy, tak, powinieneś być w stanie wykonać za pomocą tego dowolny kod powłoki.
wjl

22

możesz spróbować

echo $(eval echo $(cat something.txt))

9
Użyj, echo -e "$(eval "echo -e \"`<something.txt`\"")"jeśli chcesz zachować nowe linie.
pevik

Działa jak urok
kyb

1
Ale tylko wtedy, gdy template.txtjest to tekst. Ta operacja jest niebezpieczna, ponieważ wykonuje (ewaluuje) polecenia, jeśli są.
kyb

2
ale to usunęło podwójny cudzysłów
Thomas Decaux

Winić za to podpowłokę.
ingyhere

8

Nie chcesz drukować każdego wiersza, chcesz go oszacować , aby Bash mógł wykonywać podstawienia zmiennych.

FOO=42
while read; do
    eval echo "$REPLY"
done < something.txt

Aby help evaluzyskać więcej informacji, zobacz lub podręcznik Bash.


2
evaljest niebezpieczny ; zostaniesz nie tylko $varnamerozszerzony (tak jak w przypadku envsubst), ale $(rm -rf ~)także.
Charles Duffy,

4

Inne podejście (które wydaje się okropne, ale i tak je tutaj umieszczam):

Zapisz zawartość something.txt do pliku tymczasowego, z owiniętą instrukcją echo:

something=$(cat something.txt)

echo "echo \"" > temp.out
echo "$something" >> temp.out
echo "\"" >> temp.out

następnie wróć do zmiennej:

RESULT=$(source temp.out)

a $ RESULT rozszerzy to wszystko. Ale wydaje się to takie złe!


1
okropne, ale jest najbardziej proste, proste i działa.
kyb

3

Jeśli chcesz tylko rozszerzyć odwołania do zmiennych (cel, który miałem dla siebie), możesz wykonać poniższe czynności.

contents="$(cat something.txt)"
echo $(eval echo \"$contents\")

(Kluczowe znaczenie mają tutaj cudzysłowy otaczające zawartość $)


Próbowałem tego, jednak $ () usuwa zakończenia linii w moim przypadku, co powoduje przerwanie otrzymanego ciągu
moz

Zmieniłem eval linię eval "echo \"$$contents\""i zachowane odstępy
moz

1

Następujące rozwiązanie:

  • umożliwia podmianę zdefiniowanych zmiennych

  • pozostawia niezmienione zmienne zastępcze, które nie są zdefiniowane. Jest to szczególnie przydatne podczas zautomatyzowanych wdrożeń.

  • obsługuje zamianę zmiennych w następujących formatach:

    ${var_NAME}

    $var_NAME

  • informuje, które zmienne nie są zdefiniowane w środowisku i zwraca kod błędu dla takich przypadków



    TARGET_FILE=someFile.txt;
    ERR_CNT=0;

    for VARNAME in $(grep -P -o -e '\$[\{]?(\w+)*[\}]?' ${TARGET_FILE} | sort -u); do     
      VAR_VALUE=${!VARNAME};
      VARNAME2=$(echo $VARNAME| sed -e 's|^\${||g' -e 's|}$||g' -e 's|^\$||g' );
      VAR_VALUE2=${!VARNAME2};

      if [ "xxx" = "xxx$VAR_VALUE2" ]; then
         echo "$VARNAME is undefined ";
         ERR_CNT=$((ERR_CNT+1));
      else
         echo "replacing $VARNAME with $VAR_VALUE2" ;
         sed -i "s|$VARNAME|$VAR_VALUE2|g" ${TARGET_FILE}; 
      fi      
    done

    if [ ${ERR_CNT} -gt 0 ]; then
        echo "Found $ERR_CNT undefined environment variables";
        exit 1 
    fi

1
  1. Jeśli coś.txt ma tylko jedną linię, bashmetodę (krótszą wersję „okropnej” odpowiedzi Michaela Neale'a ), używając podstawiania procesów i poleceń :

    FOO=42 . <(echo -e echo $(<something.txt))
    

    Wynik:

    hello 42 world
    

    Pamiętaj, że exportnie jest to potrzebne.

  2. Jeśli something.txt ma jeden lub więcej wierszy, a GNU wyceny metodę:sed e

    FOO=42 sed 's/"/\\\"/g;s/.*/echo "&"/e' something.txt
    

1
Uważam, że sedocena najlepiej pasuje do mojego celu. Musiałem wyprodukować skrypty z szablonów, które miałyby wypełnione wartości ze zmiennych wyeksportowanych w innym pliku konfiguracyjnym. Obsługuje poprawnie ucieczkę w celu rozwinięcia poleceń, np. Umieszczenie \$(date)w szablonie, aby uzyskać $(date)wynik. Dzięki!
gadamiak

0
$ eval echo $(cat something.txt)
hello 42 world
$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.

0
foo=45
file=something.txt       # in a file is written: Hello $foo world!
eval echo $(cat $file)

1
Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie byłoby znacznie poprawić swoją długoterminową wartość pokazując dlaczego jest to dobre rozwiązanie problemu i byłoby bardziej użyteczne dla czytelników przyszłości z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
Koziorożec

-1

envsubst jest świetnym rozwiązaniem (patrz odpowiedź LenW), jeśli zastępowana treść ma „rozsądną” długość.

W moim przypadku potrzebowałem podstawić w zawartości pliku, aby zastąpić nazwę zmiennej. envsubstwymaga, aby zawartość została wyeksportowana jako zmienne środowiskowe, a bash ma problem podczas eksportowania zmiennych środowiskowych, które są większe niż jeden megabajt.

awk rozwiązanie

Korzystanie z rozwiązania Cuonglm z innego pytania:

needle="doc1_base64" # The "variable name" in the file. (A $ is not needed.)
needle_file="doc1_base64.txt" # Will be substituted for the needle 
haystack=$requestfile1 # File containing the needle
out=$requestfile2
awk "BEGIN{getline l < \"${needle_file}\"}/${needle}/{gsub(\"${needle}\",l)}1" $haystack > $out

To rozwiązanie działa nawet w przypadku dużych plików.


-3

Następujące prace: bash -c "echo \"$(cat something.txt)"\"


Jest to nie tylko nieefektywne, ale także niezwykle niebezpieczne; nie tylko uruchamia bezpieczne rozszerzenia, jak $foo, ale niebezpieczne, takie jak $(rm -rf ~).
Charles Duffy,
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.