Как отложить расширение переменной

18

Я хотел инициализировать некоторые строки в верхней части моего скрипта с переменными, которые еще не были установлены, например:

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

а потом на PLACE, EVENT, ACTIONи RESULTбудет установлен. Затем я хочу распечатать мои строки с расширенными переменными. Это мой единственный вариант eval? Это похоже на работу:

eval "echo ${str1}"

это стандарт? Есть лучший способ это сделать? Было бы неплохо не работать, evalучитывая, что переменные могут быть чем угодно.

Аарон
источник

Ответы:

23

С указанным типом ввода единственный способ использовать расширение оболочки для подстановки значений в строку - это использовать evalв некоторой форме. Это безопасно, если вы контролируете значение str1и можете гарантировать, что оно ссылается только на переменные, которые известны как безопасные (не содержащие конфиденциальные данные), и не содержит никаких других специальных символов без кавычек оболочки. Вы должны раскрыть строку внутри двойных кавычек или в документе здесь, так как это только "$\`специальные (перед ними должна стоять буква \in str1).

eval "substituted=\"$str1\""

Было бы намного надежнее определить функцию вместо строки.

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

Установите переменные, затем вызовите функцию fill_templateдля установки выходных переменных.

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
Хорошая работа с использованием функции, чтобы задержать оценку и избежать явного вызова eval.
Клейтон Стэнли
Хорошее решение, это мне очень помогло. Благодарность!
Стюарт
8

Поскольку я понимаю ваше значение, я не верю, что какой-либо из этих ответов является правильным. evalв этом нет необходимости, и вам даже не нужно даже дважды оценивать ваши переменные.

Это правда, @Gilles подходит очень близко, но он не рассматривает проблему возможного переопределения значений и того, как они должны использоваться, если они вам нужны более одного раза. В конце концов, шаблон должен использоваться более одного раза, верно?

Я думаю, важен порядок, в котором вы оцениваете их. Учтите следующее:

ВЕРХ

Здесь вы установите некоторые значения по умолчанию и подготовитесь распечатать их при вызове ...

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

БЛИЖНИЙ

Здесь вы определяете другие функции для вызова вашей функции печати на основе их результатов ...

    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
    }

ДНО

Теперь у вас все настроено, так что вот где вы будете выполнять и извлекать результаты.

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

РЕЗУЛЬТАТЫ

Я пойду, почему через мгновение, но выполнение выше дает следующие результаты:

_less_important_function()'s первый забег:

Я пошел в дом твоей матери и увидел Диснея на льду.

Если вы будете заниматься каллиграфией, у вас все получится.

тогда _more_important_function():

Я пошел на кладбище и увидел Дисней на льду.

Если вы делаете корректирующую математику, у вас все получится.

_less_important_function() опять таки:

Я пошел на кладбище и увидел Дисней на льду.

Если вы занимаетесь коррективной математикой, вы об этом пожалеете.

КАК ЭТО УСТРОЕНО:

Ключевой особенностью здесь является концепция « conditional ${parameter} expansion.Вы можете установить переменную в значение только в том случае, если она не установлена ​​или равна нулю, используя форму:

${var_nameзнак равноdesired_value}

Если вместо этого вы захотите установить только неустановленную переменную, вы бы пропустили :colonзначения null и остались бы как есть.

НА ОБЛАСТИ:

Вы можете заметить это в приведенном выше примере $PLACEи $RESULTизменить его, если настроено через, parameter expansionхотя _top_of_script_pr()он уже был вызван, предположительно устанавливая их при запуске. Причина, по которой это работает, заключается в том, что _top_of_script_pr()это ( subshelled )функция - я включил ее, parensа не { curly braces }использовал для других. Поскольку он вызывается в подоболочке, каждая переменная, которую он устанавливает, имеет значение, locally scopedи при возврате в родительскую оболочку эти значения исчезают.

Но когда _more_important_function()наборы $ACTIONэто globally scopedтак, это влияет на _less_important_function()'sвторую оценку, $ACTIONпотому что _less_important_function()наборы $ACTIONтолько через${parameter:=expansion}.

:ЗНАЧЕНИЕ NULL

И почему я использую ведущий :colon?Ну, manстраница скажет вам, что : does nothing, gracefully.вы видите, parameter expansionэто именно то, на что это похоже - это expandsзначение ${parameter}.So. Когда мы устанавливаем переменную с, ${parameter:=expansion}мы остаемся с ее значением - что оболочка будет попытка выполнить в строке. Если бы он попытался запустить, the cemeteryон бы просто наплевал на вас. PLACE="${PLACE:="the cemetery"}"будет давать те же результаты, но это также избыточно в этом случае, и я предпочел, чтобы оболочка: ${did:=nothing, gracefully}.

Это позволяет вам сделать это:

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

ЗДЕСЬ-ДОКУМЕНТЫ

И, между прочим, встроенное определение нулевой или неустановленной переменной также является причиной того, что работает следующее:

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

Лучший способ думать об этом here-document - это фактический файл, переданный во входной дескриптор файла. Более или менее это то, чем они являются, но разные оболочки реализуют их немного по-разному.

В любом случае, если вы не заключите в кавычки значение, <<LIMITERвы получите его в потоковом режиме и оцените таким expansion.образом. Объявление переменной в банке here-documentможет сработать, но только с помощью expansionкоторой вы ограничиваете установку только тех переменных, которые еще не установлены. Тем не менее, это идеально соответствует вашим потребностям, как вы их описали, поскольку ваши значения по умолчанию всегда будут установлены при вызове функции печати шаблона.

ПОЧЕМУ НЕТ eval?

Хорошо, пример, который я представил, предоставляет безопасные и эффективные средства принятия. parameters.Поскольку он обрабатывает область действия, каждая переменная в наборе via ${parameter:=expansion}может быть определена извне. Итак, если вы поместите все это в скрипт с именем template_pr.sh и запустите:

 % RESULT=something_else template_pr.sh

Вы получите:

Я пошел в дом твоей матери и увидел Диснея на льду

Если вы будете заниматься каллиграфией, то будете

Я пошел на кладбище и увидел Дисней на льду

Если вы делаете корректирующую математику, вы будете что-то

Я пошел на кладбище и увидел Дисней на льду

Если вы делаете корректирующую математику, вы будете что-то

Это не будет работать для тех переменных , которые были буквально установлены в сценарии, такие как $EVENT, $ACTION,и , $one,но я только определенных тем , таким образом , чтобы продемонстрировать разницу.

В любом случае принятие неизвестных входных данных в evaledутверждение по своей сути небезопасно, тогда как parameter expansionоно специально предназначено для этого.

mikeserv
источник
1

Вы можете использовать заполнители для шаблонов строк вместо нерасширенных переменных. Это очень быстро запутается. Если то, что вы делаете, очень тяжело с шаблонами, вы можете рассмотреть язык с реальной библиотекой шаблонов.

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"

Недостатком вышесказанного является то, что переменная шаблона должна быть ее собственным словом (например, вы не можете сделать это "%prefix%foo"). Это можно исправить с помощью некоторых модификаций или просто жестко закодировать переменную шаблона вместо того, чтобы она была динамической.

jordanm
источник