Jak odroczyć rozszerzenie zmiennej

18

Chciałem zainicjować niektóre ciągi na początku mojego skryptu za pomocą zmiennych, które nie zostały jeszcze ustawione, takich jak:

str1='I went to ${PLACE} and saw ${EVENT}'
str2='If you do ${ACTION} you will ${RESULT}'

a później na PLACE, EVENT, ACTION, i RESULTbędzie ustawiony. Chcę wtedy móc wydrukować moje łańcuchy z rozwiniętymi zmiennymi. Czy moja jedyna opcja eval? To wydaje się działać:

eval "echo ${str1}"

czy to standard? czy jest na to lepszy sposób? Byłoby miło nie uruchamiać, evalponieważ zmienne mogą być cokolwiek.

Aaron
źródło

Odpowiedzi:

23

W przypadku rodzaju danych, które pokazujesz, jedynym sposobem na wykorzystanie rozszerzenia powłoki do zastąpienia wartości w łańcuchu jest użycie evalw jakiejś formie. Jest to bezpieczne, pod warunkiem, że kontrolujesz wartość str1i możesz zapewnić, że odwołuje się tylko do zmiennych, które są znane jako bezpieczne (nie zawierają poufnych danych) i nie zawiera żadnych innych niecytowanych znaków specjalnych powłoki. Powinieneś rozwinąć ciąg znaków wewnątrz podwójnych cudzysłowów lub w dokumencie tutaj, w ten sposób "$\`są tylko wyjątkowe (muszą być poprzedzone znakiem \in str1).

eval "substituted=\"$str1\""

Definiowanie funkcji zamiast łańcucha byłoby znacznie bardziej niezawodne.

fill_template () {
  sentence1="I went to ${PLACE} and saw ${EVENT}"
  sentence2="If you do ${ACTION} you will ${RESULT}"
}

Ustaw zmienne, a następnie wywołaj funkcję, fill_templateaby ustawić zmienne wyjściowe.

PLACE=Sydney; EVENT=fireworks
ACTION='not learn from history'; RESULT='have to relive history'
fill_template
echo "During my holidays, $sentence1."
echo "Cicero said: \"$sentence2\"."
Gilles „SO- przestań być zły”
źródło
2
Dobra robota przy użyciu funkcji opóźniającej ocenę i unikającej jawnego wywołania eval.
Clayton Stanley,
Dobre rozwiązanie, bardzo mi pomogło. Dzięki!
Stuart
8

Biorąc pod uwagę twoje znaczenie, nie sądzę, aby którakolwiek z tych odpowiedzi była poprawna. evalnie jest w żaden sposób konieczne, ani nie musisz nawet dwukrotnie oceniać swoich zmiennych.

To prawda, @Gilles jest bardzo blisko, ale nie rozwiązuje problemu potencjalnie nadpisujących wartości i tego, jak należy ich używać, jeśli potrzebujesz ich więcej niż jeden raz. W końcu szablon powinien być używany więcej niż raz, prawda?

Myślę, że ważniejsza jest kolejność ich oceniania. Rozważ następujące:

TOP

Tutaj ustawisz niektóre wartości domyślne i przygotujesz się do ich wydrukowania po wywołaniu ...

#!/bin/sh
    _top_of_script_pr() ( 
        IFS="$nl" ; set -f #only split at newlines and don't expand paths
        printf %s\\n ${strings}
    ) 3<<-TEMPLATES
        ${nl=
}
        ${PLACE:="your mother's house"}
        ${EVENT:="the unspeakable."}
        ${ACTION:="heroin"}
        ${RESULT:="succeed."}
        ${strings:="
            I went to ${PLACE} and saw ${EVENT}
            If you do ${ACTION} you will ${RESULT}
        "}
    #END
    TEMPLATES

ŚRODKOWY

Tutaj definiujesz inne funkcje, które mają być wywoływane w funkcji drukowania na podstawie ich wyników ...

    EVENT="Disney on Ice."
    _more_important_function() { #...some logic...
        [ $((1+one)) -ne 2 ] && ACTION="remedial mathematics"
            _top_of_script_pr
    }
    _less_important_function() { #...more logic...
        one=2
        : "${ACTION:="calligraphy"}"
        _top_of_script_pr
    }

DOLNY

Masz już wszystko skonfigurowane, więc tutaj wykonasz i wyciągniesz wyniki.

    _less_important_function
    : "${PLACE:="the cemetery"}" 
    _more_important_function
    : "${RESULT:="regret it."}" 
    _less_important_function    

WYNIKI

Zaraz się zastanowię, dlaczego, ale uruchomienie powyższego daje następujące wyniki:

_less_important_function()'s pierwszy bieg:

Poszedłem do domu twojej matki i zobaczyłem Disneya na lodzie.

Jeśli wykonasz kaligrafię , odniesiesz sukces.

następnie _more_important_function():

Poszedłem na cmentarz i zobaczyłem Disneya na lodzie.

Jeśli wykonasz matematykę naprawczą , odniesiesz sukces.

_less_important_function() jeszcze raz:

Poszedłem na cmentarz i zobaczyłem Disneya na lodzie.

Jeśli uczysz matematyki naprawczej, będziesz tego żałować.

JAK TO DZIAŁA:

Kluczową cechą tutaj jest koncepcja conditional ${parameter} expansion.Można ustawić zmienną na wartość tylko wtedy, gdy jest ona nieustawiona lub zerowa przy użyciu formularza:

${var_name: =desired_value}

Jeśli zamiast tego chcesz ustawić tylko zmienną nieuzbrojoną, pominiesz, :colona wartości null pozostaną bez zmian.

W ZAKRESIE:

Możesz zauważyć to w powyższym przykładzie $PLACEi $RESULTzmienić się po ustawieniu za pośrednictwem, parameter expansionnawet jeśli _top_of_script_pr()zostało już wywołane, prawdopodobnie ustawiając je po uruchomieniu. Powodem, dla którego to działa, _top_of_script_pr()jest ( subshelled )funkcja - ja ją zawarłem, parensa nie { curly braces }użyłem dla innych. Ponieważ jest wywoływana w podpowłoce, każda ustawiona przez nią zmienna jest, locally scopeda po powrocie do powłoki macierzystej wartości te znikają.

Ale kiedy _more_important_function()zestawy $ACTIONjest globally scopedwięc wpływa na _less_important_function()'sdrugą ocenę $ACTIONponieważ _less_important_function()zestawów $ACTIONtylko poprzez${parameter:=expansion}.

:ZERO

I dlaczego używam wiodącej :colon?Cóż, manstrona powie ci, że : does nothing, gracefully.widzisz, parameter expansionjest dokładnie tak, jak to brzmi - expandsdo wartości ${parameter}.So Więc kiedy ustawimy zmienną ${parameter:=expansion}, pozostanie nam jej wartość - którą powłoka będzie próba wykonania in-line. Gdyby próbował uruchomić the cemetery, wyplułby na ciebie kilka błędów. PLACE="${PLACE:="the cemetery"}"przyniosłoby takie same wyniki, ale w tym przypadku jest również zbędne i wolałem powłokę: ${did:=nothing, gracefully}.

Pozwala ci to zrobić:

    echo ${var:=something or other}
    echo $var
something or other
something or other

TUTAJ DOKUMENTY

Nawiasem mówiąc - wbudowana definicja zmiennej zerowej lub nieuzbrojonej jest również przyczyną następujących działań:

    <<HEREDOC echo $yo
        ${yo=yoyo}
    HEREDOC
yoyo

Najlepszym sposobem, aby myśleć o tym, here-document jest rzeczywisty plik przesyłany strumieniowo do deskryptora pliku wejściowego. Mniej więcej takie są, ale różne powłoki implementują je nieco inaczej.

W każdym razie, jeśli nie zacytujesz <<LIMITER, dostajesz go strumieniowo i oceniasz pod kątem expansion.Więc zadeklarowanie zmiennej w here-documentpuszce może działać, ale tylko przez to expansionogranicza cię do ustawiania tylko zmiennych, które nie są jeszcze ustawione. Mimo to idealnie odpowiada Twoim potrzebom, tak jak je opisałeś, ponieważ domyślne wartości będą zawsze ustawione po wywołaniu funkcji drukowania szablonu.

DLACZEGO NIE eval?

Cóż, przykład, który przedstawiłem, zapewnia bezpieczny i skuteczny sposób akceptacji parameters.Ponieważ obsługuje zakres, każdą zmienną w zestawie ${parameter:=expansion}można definiować z zewnątrz. Jeśli więc umieścisz to wszystko w skrypcie o nazwie template_pr.sh i uruchomisz:

 % RESULT=something_else template_pr.sh

Dostałbyś:

Poszedłem do domu twojej matki i zobaczyłem Disneya na lodzie

Jeśli wykonasz kaligrafię, zrobisz coś innego

Poszedłem na cmentarz i zobaczyłem Disneya na lodzie

Jeśli zajmujesz się matematyką zaradczą, zrobisz coś innego

Poszedłem na cmentarz i zobaczyłem Disneya na lodzie

Jeśli zajmujesz się matematyką zaradczą, zrobisz coś innego

Nie działałoby to dla tych zmiennych, które zostały dosłownie ustawione w skrypcie, takich jak $EVENT, $ACTION,i, $one,ale zdefiniowałem je tylko w ten sposób, aby pokazać różnicę.

W każdym razie akceptacja nieznanych danych wejściowych w evaledinstrukcji jest z natury niebezpieczna, podczas gdy parameter expansionjest specjalnie zaprojektowana do tego.

mikeserv
źródło
1

Można używać symboli zastępczych dla szablonów ciągów zamiast nierozwiniętych zmiennych. Szybko się zepsuje. Jeśli to, co robisz, jest bardzo obciążone szablonami, możesz rozważyć język z prawdziwą biblioteką szablonów.

format_template() {
    changed_str=$1

    for word in $changed_str; do
        if [[ $word == %*% ]]; then
            var="${word//\%/}"
            changed_str="${changed_str//$word/${!var}}"
        fi
    done
}

str1='I went to %PLACE% and saw %EVENT%'
PLACE="foo"
EVENT="bar"
format_template "$str1"
echo "$changed_str"

Minusem powyższego jest to, że zmienna szablonu musi być własnym słowem (np. Nie możesz tego zrobić "%prefix%foo"). Można to naprawić za pomocą pewnych modyfikacji lub po prostu przez stałe zakodowanie zmiennej szablonu zamiast dynamicznej.

Jordan
źródło