Como adiar a expansão de variáveis

15

Eu queria inicializar algumas strings no topo 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, mais tarde, PLACE , EVENT , ACTION e RESULT serão definidos. Eu quero então poder imprimir minhas strings com as variáveis expandidas. É minha única opção eval ? Isso parece funcionar:

eval "echo ${str1}"

é esse padrão? Existe uma maneira melhor de fazer isso? Seria bom não executar eval considerando que as variáveis poderiam ser qualquer coisa.

    
por Aaron 09.01.2013 / 00:20

3 respostas

21

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 eval de alguma forma. Isso é seguro desde que você controle o valor de str1 e possa garantir que ele apenas faça referência a variáveis conhecidas como seguras (que não contêm dados confidenciais) e não contenha nenhum caractere especial de shell sem aspas. Você deve expandir a string entre aspas duplas ou em um documento aqui, assim somente "$\' são especiais (eles precisam ser precedidos por \ in 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_template para 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\"."
    
por 09.01.2013 / 01:36
7

Como eu entendo o seu significado, não acredito que nenhuma dessas respostas esteja correta. eval não é necessário de forma alguma, nem você precisa sequer avaliar duas vezes suas variáveis.

É verdade que @Gilles chega muito perto, mas ele não resolve o problema de possivelmente sobrepor 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 em que você os avalia que é importante. Considere o seguinte:

TOP

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

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

MÉDIO

Aqui é onde 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
    }

FUNDO

Você tem tudo configurado agora, então é aqui que você executará e extrairá seus resultados.

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

RESULTADOS

Vou falar sobre o porquê daqui a pouco, mas a execução acima produz os seguintes resultados:

_less_important_function()'s first run:

I went to your mother's house and saw Disney on Ice.

If you do calligraphy you will succeed.

     

então _more_important_function():

     

I went to the cemetery and saw Disney on Ice.

If you do remedial mathematics you will succeed.

     

_less_important_function() novamente:

     

I went to the cemetery and saw Disney on Ice.

If you do remedial mathematics you will regret it.

COMO FUNCIONA:

A principal característica aqui é o conceito de conditional ${parameter} expansion. Você pode definir uma variável para um valor somente se ela não estiver definida ou nula usando o formulário:

${var_name:=desired_value}

Se, em vez disso, você quiser definir apenas uma variável não definida, omita o :colon e os valores nulos permanecerão como estão.

ON SCOPE:

Você pode notar que no exemplo acima $PLACE e $RESULT são alterados quando definidos via parameter expansion embora < em> _top_of_script_pr() já foi chamado, presumivelmente definindo-os quando é executado. A razão pela qual isso funciona é que _top_of_script_pr() é uma função ( subshelled ) - coloquei em parens em vez de { curly braces } usado para os outros. Como ele é chamado em um subshell, cada variável que ele configura é locally scoped e, quando retorna ao shell pai, esses valores desaparecem.

Mas quando _more_important_function() define $ACTION , é globally scoped , portanto, afeta _less_important_function()'s segunda avaliação de $ACTION porque _less_important_function() define $ACTION somente por ${parameter:=expansion}.

: NULL

E por que eu uso o :colon? ? Bem, a página man diz que : does nothing, gracefully. , parameter expansion é exatamente o que parece - é expands com o valor ${parameter}. Então quando definimos uma variável com ${parameter:=expansion} ficamos com seu valor - que o shell tentará executar em linha. Se ele tentou rodar the cemetery , ele iria cuspir 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

E a propósito - a definição em linha de uma variável nula ou não definida é também a razão pela qual os seguintes trabalhos:

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

A melhor maneira de pensar em um here-document é como um arquivo real transmitido para um descritor de arquivo de entrada. Mais ou menos isso é o que eles são, mas diferentes shells os implementam de maneira um pouco diferente.

Em qualquer caso, se você não citar o <<LIMITER , ele será transmitido em e avaliado em expansion. Portanto, declarar uma variável em um here-document pode funcionar, mas somente via expansion , o que limita a configuração apenas de variáveis que ainda não estão definidas . Ainda assim, isso se ajusta perfeitamente às suas necessidades como você as descreveu, já que os valores padrão sempre serão definidos quando você chamar a função de impressão do modelo.

POR QUE NÃO eval?

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

 % RESULT=something_else template_pr.sh

Você teria:

I went to your mother's house and saw Disney on Ice

If you do calligraphy you will something_else

I went to the cemetery and saw Disney on Ice

If you do remedial mathematics you will something_else

I went to the cemetery and saw Disney on Ice

If you do remedial mathematics you will something_else

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

Em qualquer caso, a aceitação de entrada desconhecida em uma instrução evaled é inerentemente insegura, enquanto parameter expansion é especificamente projetada para fazê-lo.

    
por 18.03.2014 / 04:36
1

Você pode usar espaços reservados para modelos de string em vez de variáveis não expandidas. Isso vai ficar confuso rapidamente. Se o que você está fazendo é muito pesado, você pode querer considerar uma linguagem 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 item acima é que a variável de 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 de modelo em vez de dinâmica.

    
por 09.01.2013 / 00:44