Como adiar a expansão variável

18

Eu estava querendo inicializar algumas strings na parte superior do meu script com variáveis ​​que ainda não foram definidas, como:

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

e, em seguida, mais tarde PLACE, EVENT, ACTION, e RESULTserá definido. Quero poder imprimir minhas strings com as variáveis ​​expandidas. É minha única opção eval? Isso parece funcionar:

eval "echo ${str1}"

isso é padrão? Existe uma maneira melhor de fazer isso? Seria bom não executar, evalconsiderando que as variáveis ​​podem ser qualquer coisa.

Aaron
fonte

Respostas:

23

Com o tipo de entrada que você mostra, a única maneira de aproveitar a expansão do shell para substituir valores em uma string é usar evalde alguma forma. Isso é seguro, desde que você controle o valor str1e garanta que ele faça referência apenas a variáveis ​​conhecidas como seguras (que não contêm dados confidenciais) e que não contenha nenhum outro caractere especial do shell não citado. Você deve expandir a string dentro de aspas ou em um documento aqui, de que maneira única "$\`são especiais (eles precisam ser precedido por um \em str1).

eval "substituted=\"$str1\""

Seria muito mais robusto definir uma função em vez de uma string.

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

Defina as variáveis ​​e chame a função fill_templatepara definir as variáveis ​​de saída.

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- parar de ser mau'
fonte
2
Bom trabalho usando uma função para atrasar a avaliação e evitar a chamada de avaliação explícita.
Clayton Stanley
Boa solução, isso me ajudou muito. Obrigado!
Stuart
8

Pelo que entendi, não acredito que nenhuma dessas respostas esteja correta. evalnão é necessário de forma alguma, nem você precisa avaliar duas vezes suas variáveis.

É verdade que o @Gilles chega muito perto, mas ele não aborda o problema de possivelmente substituir valores e como eles devem ser usados ​​se você precisar deles mais de uma vez. Afinal, um modelo deve ser usado mais de uma vez, certo?

Eu acho que é mais a ordem na qual você os avalia que é importante. Considere o seguinte:

TOPO

Aqui você definirá alguns padrões e se preparará para imprimi-los quando chamados ...

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

MEIO

É aqui que você define outras funções para chamar sua função de impressão com base em seus resultados ...

    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
    }

INFERIOR

Agora você tem tudo configurado, então aqui é onde você executa e obtém seus resultados.

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

RESULTADOS

Falarei sobre o motivo daqui a pouco, mas a execução do procedimento acima produz os seguintes resultados:

_less_important_function()'s Primeira corrida:

Fui à casa de sua mãe e vi Disney on Ice.

Se você fizer caligrafia, terá sucesso.

então _more_important_function():

Fui ao cemitério e vi Disney on Ice.

Se você praticar matemática corretiva, terá sucesso.

_less_important_function() novamente:

Fui ao cemitério e vi Disney on Ice.

Se você pratica matemática corretiva, vai se arrepender.

COMO FUNCIONA:

O principal recurso aqui é o conceito de conditional ${parameter} expansion.Você pode definir uma variável para um valor somente se estiver desconfigurada ou nula usando o formulário:

${var_name: =desired_value}

Se, em vez disso, você desejar definir apenas uma variável não configurada, você omitirá os :colonvalores nulos e permanecerá como está.

NO ÂMBITO DE APLICAÇÃO:

Você pode perceber isso no exemplo acima $PLACEe $RESULTser alterado quando definido via parameter expansionmesmo que _top_of_script_pr()já tenha sido chamado, presumivelmente configurando-os quando é executado. A razão pela qual isso funciona é que _top_of_script_pr()é uma ( subshelled )função - eu a incluí em parensvez da { curly braces }usada para as outras. Como é chamado em um subshell, todas as variáveis ​​que ele define são locally scopede, quando retornam ao shell pai, esses valores desaparecem.

Mas quando _more_important_function()conjuntos $ACTION, globally scopedisso afeta a _less_important_function()'ssegunda avaliação, $ACTIONporque _less_important_function()conjuntos $ACTIONsomente via${parameter:=expansion}.

:NULO

E por que eu uso o líder :colon?Bem, a manpágina dirá a você que : does nothing, gracefully.você vê, parameter expansioné exatamente o que parece - expandsao valor do ${parameter}.Então, quando definimos uma variável, ${parameter:=expansion}ficamos com seu valor - que o shell exibirá tente executar em linha. Se tentasse executar the cemetery, cuspiria alguns erros em você. PLACE="${PLACE:="the cemetery"}"produziria os mesmos resultados, mas também é redundante neste caso e eu preferi que o shell: ${did:=nothing, gracefully}.

Ele permite que você faça isso:

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

AQUI-DOCUMENTOS

A propósito, a definição em linha de uma variável nula ou não definida também é o motivo pelo qual o seguinte funciona:

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

A melhor maneira de pensar em a here-document é como um arquivo real transmitido para um descritor de arquivo de entrada. É mais ou menos isso, mas conchas diferentes as implementam de maneira um pouco diferente.

De qualquer forma, se você não citar o <<LIMITERtexto, ele será transmitido e avaliado como expansion.So declarar uma variável em um here-documentpode funcionar, mas apenas através do expansionqual você limita a definir apenas variáveis ​​que ainda não estão definidas. Ainda assim, isso se adapta perfeitamente às suas necessidades conforme você as descreveu, pois seus valores padrão sempre serão definidos quando você chama sua função de impressão de modelo.

POR QUE NÃO eval?

Bem, o exemplo que apresentei fornece um meio seguro e eficaz de aceitar. parameters.Como ele lida com o escopo, todas as variáveis ​​dentro do conjunto via ${parameter:=expansion}são definíveis de fora. Portanto, se você colocar tudo isso em um script chamado template_pr.sh e executar:

 % RESULT=something_else template_pr.sh

Você obteria:

Fui à casa de sua mãe e vi Disney on Ice

Se você fizer caligrafia, irá

Eu fui ao cemitério e vi Disney on Ice

Se você pratica matemática corretiva, irá

Eu fui ao cemitério e vi Disney on Ice

Se você pratica matemática corretiva, irá

Isso não funcionaria para as variáveis ​​que foram literalmente definidas no script, como $EVENT, $ACTION,e, $one,mas eu apenas as defini dessa maneira para demonstrar a diferença.

Em qualquer caso, a aceitação de informações desconhecidas em uma evaledinstrução é inerentemente insegura, enquanto parameter expansionfoi especificamente projetada para isso.

mikeserv
fonte
1

Você pode usar espaços reservados para modelos de string em vez de variáveis ​​não expandidas. Isso ficará confuso rapidamente. Se o que você está fazendo é muito pesado para modelos, considere um idioma com uma biblioteca de modelos real.

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"

A desvantagem do acima é que a variável do modelo deve ser sua própria palavra (por exemplo, você não pode fazer "%prefix%foo"). Isso pode ser corrigido com algumas modificações ou simplesmente codificando a variável do modelo em vez de ser dinâmica.

jordanm
fonte