변수 확장을 연기하는 방법

변수 확장을 연기하는 방법

다음과 같이 아직 설정되지 않은 변수를 사용하여 스크립트 상단의 일부 문자열을 초기화하고 싶습니다.

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

그런 다음 나중에 PLACE, EVENT, ACTIONRESULT설정됩니다. 그런 다음 확장된 변수를 사용하여 문자열을 인쇄할 수 있기를 원합니다. 그게 내 유일한 선택인가요 eval? 이것은 작동하는 것 같습니다:

eval "echo ${str1}"

이게 표준인가요? 더 좋은 방법이 있나요? eval변수가 무엇이든 될 수 있다는 점을 고려하면 실행하지 않는 것이 가장 좋습니다.

답변1

표시되는 입력 유형의 경우 셸 확장을 활용하여 값을 문자열로 바꾸는 유일한 방법은 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어쨌든 필요하지 않으며 변수를 두 번 평가할 필요도 없습니다.

실제로 @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()다시:

묘지에 가서 디즈니 온 아이스(Disney on Ice)를 봤어요.

수학을 과외하면후회.

작동 방식:

여기서 중요한 특징은 컨셉입니다.conditional ${parameter} expansion.변수가 설정되지 않았거나 null인 경우에만 다음 형식을 사용하여 변수를 값으로 설정할 수 있습니다.

${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}.

:유효하지 않은

내가 왜 선도를 사용하는가?:colon?음,man페이지에서 알려줄 것입니다.: does nothing, gracefully.바라보다,parameter expansion정확히 무슨 소리인지 - 그렇죠expands${parameter}.그래서 변수를 설정할 때${parameter:=expansion}그 값은 그대로 둡니다. 쉘은 이를 인라인으로 실행하려고 시도합니다. 실행하려고 하면the cemetery그것은 단지 당신에게 몇 가지 오류를 뱉어낼 것입니다.PLACE="${PLACE:="the cemetery"}"동일한 결과를 생성하지만 이 경우에도 중복됩니다. 저는 Shell을 선호합니다.: ${did:=nothing, gracefully}.

이를 통해 다음을 수행할 수 있습니다.

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

여기에 파일

그런데 - null 또는 설정되지 않은 변수의 인라인 정의는 다음 방법이 작동하는 이유이기도 합니다.

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

그것에 대해 생각하는 가장 좋은 방법은here-document 실제 파일이 입력 파일 설명자로 스트리밍됩니다. 그것은 다소간 다르지만 다른 쉘은 약간 다르게 구현합니다.

어쨌든 인용하지 않으면<<LIMITER스트리밍으로 받아요그리고다음과 같이 평가됨expansion.따라서 변수를 선언하십시오.here-document작동하지만 다음을 통해서만 가능합니다.expansion이는 아직 설정되지 않은 변수를 설정하는 것으로 제한됩니다. 그러나 설명하신 대로 템플릿 인쇄 기능을 호출할 때 기본값이 항상 설정되므로 이는 귀하의 요구에 완벽하게 작동합니다.

왜 안 돼eval?

글쎄요, 제가 제시한 예는 안전하고 효율적인 수락 방법을 제공합니다.parameters.범위를 다루기 때문에 집합의 각 변수는${parameter:=expansion}외부적으로 정의할 수 있습니다. 따라서 이 모든 것을 template_pr.sh라는 스크립트에 넣고 다음을 실행하면:

 % RESULT=something_else template_pr.sh

당신은 얻을 것이다:

너희 엄마 집에 가서 디즈니 온 아이스를 봤어

캘리그라피를 연습하면 뭔가가 생긴다

묘지에 가서 디즈니 온 아이스를 봤어

수학을 벼락치기하면 뭔가 얻을 수 있을 거야

묘지에 가서 디즈니 온 아이스를 봤어

수학을 벼락치기하면 뭔가 얻을 수 있을 거야

이는 스크립트에 문자 그대로 설정된 변수에는 작동하지 않습니다.$EVENT, $ACTION,그리고$one,하지만 저는 차이점을 보여주기 위해 이렇게 정의했습니다.

어쨌든 알 수 없는 입력을 수락합니다.evaled명령문은 본질적으로 안전하지 않습니다.parameter expansion이 목적을 위해 특별히 설계되었습니다.

답변3

확장되지 않은 변수 대신 문자열 템플릿에 대한 자리 표시자를 사용할 수 있습니다. 이것은 빨리 혼란스러울 수 있습니다. 수행하는 작업이 템플릿이 매우 많은 경우 실제 템플릿 라이브러리가 있는 언어를 고려할 수 있습니다.

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"

위의 단점은 템플릿 변수가 자체 단어여야 한다는 것입니다(예: you can't do "%prefix%foo"). 이는 일부 수정을 통해 해결되거나 동적으로 대신 템플릿 변수를 하드코딩하여 해결할 수 있습니다.

답변4

나는 이것을 기반으로 할 것이다수락된 답변그리고 조금 단순화하세요. fill_template전화 통화가 좀 어색한 것 같아요 . 환경 변수 대신 함수를 사용하려면 약간 다른 구문이 필요하지만(어떤 경우에는 작동하지 않을 수 있음) IMO에서는 이를 사용하는 코드를 제어하면 더 깔끔합니다.

"변수"를 함수로 정의합니다.

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

그런 다음 다음과 같이 사용하십시오.

PLACE=Sydney; EVENT=fireworks
ACTION='not learn from history'; RESULT='have to relive history'
echo "During my holidays, $(sentence1)."
echo "Cicero said: \"$(sentence2)\"."

관련 정보