Come rinviare l'espansione variabile


18

Volevo inizializzare alcune stringhe nella parte superiore del mio script con variabili che non sono state ancora impostate, come:

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

e poi in seguito PLACE, EVENT, ACTION, e RESULTverrà impostata. Voglio quindi essere in grado di stampare le mie stringhe con le variabili espanse. È la mia unica opzione eval? Questo sembra funzionare:

eval "echo ${str1}"

è questo standard? c'è un modo migliore per farlo? Sarebbe bello non funzionare evalconsiderando che le variabili potrebbero essere qualsiasi cosa.

Risposte:


23

Con il tipo di input che mostri, l'unico modo per sfruttare l'espansione della shell per sostituire i valori in una stringa è utilizzare evalin qualche modo. Questo è sicuro fintanto che controlli il valore di str1e puoi assicurarti che faccia riferimento solo a variabili conosciute come sicure (non contenenti dati riservati) e che non contengano altri caratteri speciali della shell non quotati. Dovresti espandere la stringa tra virgolette doppie o in un documento qui, in questo modo solo gli "$\`speciali (devono essere preceduti da un \in str1).

eval "substituted=\"$str1\""

Sarebbe molto più robusto definire una funzione anziché una stringa.

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

Impostare le variabili quindi chiamare la funzione fill_templateper impostare le variabili di output.

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\"."

2
Bel lavoro usando una funzione per ritardare la valutazione ed evitare la chiamata esplicita di valutazione.
Clayton Stanley,

Buona soluzione, questo mi ha aiutato molto. Grazie!
Stuart,

8

Mentre prendo il tuo significato, non credo che nessuna di queste risposte sia corretta. evalnon è necessario in alcun modo, né hai nemmeno bisogno di valutare due volte le tue variabili.

È vero, @Gilles si avvicina molto, ma non affronta il problema della possibilità di ignorare i valori e di come dovrebbero essere utilizzati se ne hai bisogno più di una volta. Dopotutto, un modello dovrebbe essere usato più di una volta, giusto?

Penso che sia più importante l'ordine in cui li valuti. Considera quanto segue:

SUPERIORE

Qui imposterai alcune impostazioni predefinite e ti preparerai a stamparle quando viene chiamato ...

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

MEDIO

Qui è dove si definiscono altre funzioni da chiamare sulla funzione di stampa in base ai loro risultati ...

    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
    }

PARTE INFERIORE

Ora hai tutto configurato, quindi ecco dove eseguirai e tirerai i risultati.

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

RISULTATI

Esaminerò il perché tra un momento, ma l'esecuzione di quanto sopra produce i seguenti risultati:

_less_important_function()'s prima corsa:

Sono andato a casa di tua madre e ho visto Disney on Ice.

Se fai calligrafia avrai successo.

poi _more_important_function():

Sono andato al cimitero e ho visto Disney on Ice.

Se fai matematica correttiva ci riuscirai.

_less_important_function() ancora:

Sono andato al cimitero e ho visto Disney on Ice.

Se fai matematica correttiva te ne pentirai.

COME FUNZIONA:

La caratteristica chiave qui è il concetto di conditional ${parameter} expansion.È possibile impostare una variabile su un valore solo se non è impostata o è nulla utilizzando il modulo:

${var_name: =desired_value}

Se invece desideri impostare solo una variabile non impostata, ometteresti i :colonvalori null e rimarrebbero così come sono.

IN CAMPO DI APPLICAZIONE:

Potresti notare che nell'esempio sopra $PLACEe $RESULTessere cambiato quando impostato tramite parameter expansionanche se _top_of_script_pr()è già stato chiamato, presumibilmente impostandoli quando viene eseguito. Il motivo per cui funziona è che _top_of_script_pr()è una ( subshelled )funzione: l'ho racchiusa in un oggetto parensanziché nell'altro { curly braces }per gli altri. Poiché viene chiamato in una subshell, ogni variabile che imposta è locally scopede quando ritorna alla sua shell padre quei valori scompaiono.

Ma quando _more_important_function()imposta $ACTIONè globally scopedcosì influenza la _less_important_function()'sseconda valutazione di $ACTIONperché _less_important_function()imposta $ACTIONsolo tramite${parameter:=expansion}.

:NULLO

E perché uso il :colon?pozzo iniziale, la manpagina ti dirà che : does nothing, gracefully.vedi, parameter expansionè esattamente come suona - expandsal valore del ${parameter}.So quando impostiamo una variabile con cui ${parameter:=expansion}rimaniamo con il suo valore - che la shell tenta di eseguire in linea. Se tentasse di eseguirlo, the cemeteryti sputerebbe degli errori. PLACE="${PLACE:="the cemetery"}"produrrebbe gli stessi risultati, ma è anche ridondante in questo caso e ho preferito che la shell: ${did:=nothing, gracefully}.

Ti permette di fare questo:

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

QUI-DOCUMENTI

E a proposito: la definizione in linea di una variabile nulla o non impostata è anche il motivo per cui:

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

Il modo migliore per pensare a here-document è come un file reale trasmesso a un descrittore di file di input. Più o meno è quello che sono, ma conchiglie diverse le implementano in modo leggermente diverso.

In ogni caso, se non citate, lo <<LIMITERottenete in streaming e valutato per expansion.So che una variabile in a here-documentpuò funzionare, ma solo attraverso ciò expansionche vi limita a impostare solo variabili che non sono già impostate. Tuttavia, si adatta perfettamente alle tue esigenze come le hai descritte, poiché i valori predefiniti verranno sempre impostati quando chiami la funzione di stampa del modello.

PERCHÈ NO eval?

Bene, l'esempio che ho presentato fornisce un mezzo sicuro ed efficace per accettare. parameters.Poiché gestisce l'ambito, ogni variabile all'interno di set via ${parameter:=expansion}è definibile dall'esterno. Quindi, se metti tutto questo in uno script chiamato template_pr.sh ed esegui:

 % RESULT=something_else template_pr.sh

Otterresti:

Sono andato a casa di tua madre e ho visto Disney on Ice

Se fai la calligrafia, farai qualcosa

Sono andato al cimitero e ho visto Disney on Ice

Se fai matematica correttiva, farai qualcosa

Sono andato al cimitero e ho visto Disney on Ice

Se fai matematica correttiva, farai qualcosa

Questo non funzionerebbe per quelle variabili che sono state letteralmente impostate nello script, come $EVENT, $ACTION,e $one,ma ho definito solo quelle in quel modo per dimostrare la differenza.

In ogni caso, l'accettazione di input sconosciuti in evaledun'istruzione è intrinsecamente pericolosa, mentre parameter expansionè specificamente progettata per farlo.


1

È possibile utilizzare segnaposto per modelli di stringa anziché variabili non espanse. Questo diventerà disordinato abbastanza rapidamente. Se quello che stai facendo è molto pesante, potresti prendere in considerazione una lingua con una vera libreria di modelli.

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"

L'aspetto negativo di quanto sopra è che la variabile template deve essere la sua stessa parola (ad es. Non puoi farlo "%prefix%foo"). Questo potrebbe essere risolto con alcune modifiche o semplicemente codificando la variabile template invece che dinamica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.