Comment différer l'expansion variable

18

Je voulais initialiser certaines chaînes en haut de mon script avec des variables qui n'ont pas encore été définies, telles que:

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

puis plus tard PLACE, EVENT, ACTIONet RESULTsera réglé. Je veux ensuite pouvoir imprimer mes chaînes avec les variables développées. Est ma seule option eval? Cela semble fonctionner:

eval "echo ${str1}"

est-ce standard? Y a-t-il une meilleure manière de faire cela? Ce serait bien de ne pas exécuter evalcar les variables pourraient être n'importe quoi.

Aaron
la source

Réponses:

23

Avec le type d'entrée que vous montrez, la seule façon de tirer parti de l'expansion du shell pour substituer des valeurs dans une chaîne est d'utiliser evalsous une forme quelconque. C'est sûr tant que vous contrôlez la valeur de str1et pouvez vous assurer qu'il ne fait référence qu'à des variables dites sûres (ne contenant pas de données confidentielles) et ne contient aucun autre caractère spécial de shell non cité. Vous devez développer la chaîne entre guillemets doubles ou dans un document ici, de cette façon seuls "$\`sont spéciaux (ils doivent être précédés d'un \in str1).

eval "substituted=\"$str1\""

Il serait beaucoup plus robuste de définir une fonction au lieu d'une chaîne.

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

Définissez les variables puis appelez la fonction fill_templatepour définir les variables de sortie.

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- arrête d'être méchant'
la source
2
Beau travail en utilisant une fonction pour retarder l'évaluation et pour éviter l'appel eval explicite.
Clayton Stanley
Bonne solution, cela m'a beaucoup aidé. Merci!
Stuart
8

Si je comprends bien votre sens, je ne crois pas que ces réponses soient correctes. evaln'est en aucun cas nécessaire, et vous n'avez même pas besoin d'évaluer deux fois vos variables.

C'est vrai, @Gilles s'en approche de très près, mais il ne résout pas le problème de la substitution éventuelle des valeurs et comment elles devraient être utilisées si vous en avez besoin plusieurs fois. Après tout, un modèle doit être utilisé plusieurs fois, non?

Je pense que c'est plus l'ordre dans lequel vous les évaluez qui est important. Considérer ce qui suit:

HAUT

Ici, vous allez définir des valeurs par défaut et vous préparer à les imprimer lors de l'appel ...

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

MILIEU

C'est ici que vous définissez d'autres fonctions pour faire appel à votre fonction d'impression en fonction de leurs résultats ...

    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
    }

BAS

Vous avez tout configuré maintenant, alors voici où vous exécuterez et tirerez vos résultats.

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

RÉSULTATS

Je vais expliquer pourquoi dans un instant, mais l'exécution de ce qui précède produit les résultats suivants:

_less_important_function()'s première exécution:

Je suis allé chez ta mère et j'ai vu Disney sur glace.

Si vous faites de la calligraphie, vous réussirez.

ensuite _more_important_function():

Je suis allé au cimetière et j'ai vu Disney sur glace.

Si vous faites des mathématiques de rattrapage, vous réussirez.

_less_important_function() encore:

Je suis allé au cimetière et j'ai vu Disney sur glace.

Si vous faites des mathématiques correctives, vous le regretterez.

COMMENT ÇA FONCTIONNE:

La caractéristique clé ici est le concept de conditional ${parameter} expansion.Vous pouvez définir une variable sur une valeur uniquement si elle est non définie ou nulle en utilisant le formulaire:

${var_name: =desired_value}

Si, à la place, vous souhaitez définir uniquement une variable non définie, vous omettriez les :colonvaleurs nulles et resteraient telles quelles.

SUR LA PORTÉE:

Vous remarquerez peut-être que dans l'exemple ci-dessus $PLACEet que $RESULTvous vous changez lorsqu'il est défini via, parameter expansionmême s'il _top_of_script_pr()a déjà été appelé, il est probable qu'il soit défini lors de son exécution. La raison pour laquelle cela fonctionne est que _top_of_script_pr()c'est une ( subshelled )fonction - je l'ai incluse parensplutôt que celle { curly braces }utilisée pour les autres. Parce qu'elle est appelée dans un sous-shell, chaque variable qu'elle définit estlocally scoped et lorsqu'elle retourne à son shell parent, ces valeurs disparaissent.

Mais quand des _more_important_function()jeux , $ACTIONil est globally scopeddonc elle affecte _less_important_function()'sdeuxième évaluation de $ACTIONcar _less_important_function()ensembles $ACTIONuniquement par${parameter:=expansion}.

:NUL

Et pourquoi dois-je utiliser le premier :colon?Eh bien, la manpage vous dira que : does nothing, gracefully.vous voyez, parameter expansionc'est exactement ce que cela ressemble - expandsà la valeur de ${parameter}.Donc, lorsque nous définissons une variable avec ${parameter:=expansion}nous nous retrouvons avec sa valeur - que le shell tenter d'exécuter en ligne. S'il essayait de s'exécuter, the cemeteryil vous cracherait juste quelques erreurs. PLACE="${PLACE:="the cemetery"}"produirait les mêmes résultats, mais c'est aussi redondant dans ce cas et j'ai préféré que la coque: ${did:=nothing, gracefully}.

Cela vous permet de faire ceci:

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

ICI-DOCUMENTS

Et en passant - la définition en ligne d'une variable nulle ou non définie est également la raison pour laquelle les éléments suivants fonctionnent:

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

La meilleure façon de penser à un here-document est comme un fichier réel diffusé vers un descripteur de fichier d'entrée. C'est plus ou moins ce qu'ils sont, mais différents shells les implémentent légèrement différemment.

Dans tous les cas, si vous ne citez pas le, <<LIMITERvous l'obtenez en flux continu et évalué pour expansion.Ainsi, déclarer une variable dans un here-documentpeut fonctionner, mais uniquement via expansionce qui vous limite à définir uniquement des variables qui ne sont pas déjà définies. Pourtant, cela correspond parfaitement à vos besoins tels que vous les avez décrits, car vos valeurs par défaut seront toujours définies lorsque vous appelez la fonction d'impression de votre modèle.

POURQUOI PAS eval?

Eh bien, l'exemple que j'ai présenté fournit un moyen sûr et efficace d'accepter parameters.Parce qu'il gère la portée, chaque variable dans set via ${parameter:=expansion}est définissable de l'extérieur. Donc, si vous mettez tout cela dans un script appelé template_pr.sh et que vous exécutez:

 % RESULT=something_else template_pr.sh

Vous obtiendriez:

Je suis allé chez ta mère et j'ai vu Disney sur glace

Si vous faites de la calligraphie, vous aurez quelque chose_else

Je suis allé au cimetière et j'ai vu Disney sur glace

Si vous faites des mathématiques de rattrapage, vous aurez quelque chose d'autre

Je suis allé au cimetière et j'ai vu Disney sur glace

Si vous faites des mathématiques de rattrapage, vous aurez quelque chose d'autre

Cela ne fonctionnerait pas pour les variables qui ont été définies littéralement dans le script, comme $EVENT, $ACTION,et $one,mais je les ai définies de cette manière uniquement pour démontrer la différence.

Dans tous les cas, l'acceptation d'une entrée inconnue dans une evaledinstruction est intrinsèquement dangereuse, alors qu'elle parameter expansionest spécifiquement conçue pour le faire.

mikeserv
la source
1

Vous pouvez utiliser des espaces réservés pour les modèles de chaîne au lieu de variables non développées. Cela deviendra désordonné assez rapidement. Si ce que vous faites est très chargé en modèles, vous voudrez peut-être envisager un langage avec une véritable bibliothèque de modèles.

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'inconvénient de ce qui précède est que la variable de modèle doit être son propre mot (par exemple, vous ne pouvez pas le faire "%prefix%foo"). Cela pourrait être corrigé avec quelques modifications, ou simplement en codant en dur la variable de modèle au lieu d'être dynamique.

jordanm
la source