วิธีเลื่อนการขยายตัวแปร


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และสามารถมั่นใจได้ว่ามันจะอ้างอิงเฉพาะตัวแปรที่รู้จักกันว่าปลอดภัย (ไม่ได้มีข้อมูลที่เป็นความลับ) และไม่มีอักขระพิเศษอื่น ๆ คุณควรจะขยายสายภายในราคาคู่หรือในเอกสารที่นี่ว่าวิธีเดียวที่"$\`เป็นพิเศษ (พวกเขาจะต้องนำหน้าด้วย\ใน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 ที่ชัดเจน
Clayton Stanley

ทางออกที่ดีนี่ช่วยฉันได้มาก ขอบคุณ!
สจวร์

8

ขณะที่ฉันใช้ความหมายของคุณฉันไม่เชื่อว่าคำตอบใด ๆ เหล่านี้ถูกต้อง evalไม่จำเป็นต้องใช้วิธีการใด ๆ และคุณไม่จำเป็นต้องประเมินตัวแปรของคุณสองเท่า

เป็นความจริง @Gilles เข้ามาใกล้มาก แต่เขาไม่ได้แก้ไขปัญหาของค่านิยมที่อาจเอาชนะได้และวิธีการใช้หากพวกเขาต้องการมากกว่าหนึ่งครั้ง ท้ายที่สุดควรใช้เทมเพลตมากกว่าหนึ่งครั้งใช่ไหม

ฉันคิดว่ามันเป็นเรื่องที่คุณประเมินมันสำคัญมาก พิจารณาสิ่งต่อไปนี้:

TOP

ที่นี่คุณจะตั้งค่าเริ่มต้นและเตรียมพิมพ์เมื่อเรียก ...

#!/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 วิ่งครั้งแรก:

ฉันไปที่บ้านแม่ของคุณและเห็นDisney on Ice

ถ้าคุณเขียนตัวอักษรคุณจะประสบความสำเร็จ

แล้วก็ _more_important_function():

ฉันไปที่สุสานและเห็น Disney on Ice

หากคุณทำคณิตศาสตร์การแก้ไขคุณจะประสบความสำเร็จ

_less_important_function() อีกครั้ง:

ฉันไปที่สุสานและเห็น Disney on Ice

หากคุณทำคณิตศาสตร์เพื่อการเยียวยาคุณจะต้องเสียใจ

มันทำงานอย่างไร:

คุณสมบัติที่สำคัญที่นี่เป็นแนวคิดของconditional ${parameter} expansion.คุณสามารถตั้งค่าตัวแปรให้เป็นค่าเฉพาะถ้ามันเป็น unset หรือ null โดยใช้แบบฟอร์ม:

${var_name=desired_value}

หากคุณต้องการตั้งค่าเฉพาะตัวแปรที่ไม่มีการตั้งค่าคุณจะละเว้น:colonและค่า Null จะยังคงเหมือนเดิม

ในขอบเขต:

คุณอาจสังเกตเห็นว่าในตัวอย่างข้างต้น$PLACEและ$RESULTได้รับการเปลี่ยนแปลงเมื่อตั้งค่าผ่านparameter expansionแม้ว่าจะ_top_of_script_pr()ถูกเรียกไปแล้วแต่ก็น่าจะตั้งค่าพวกเขาเมื่อมันทำงาน เหตุผลที่ทำให้งานนี้_top_of_script_pr()เป็น( subshelled )ฟังก์ชั่น - ฉันใส่มันไว้ในตัวparensแทนที่จะ{ curly braces }ใช้กับคนอื่น เพราะมันถูกเรียกใน subshell ทุกตัวแปรที่มันตั้งค่าคือ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"}"จะให้ผลลัพธ์ที่เหมือนกัน แต่ก็ยังซ้ำซ้อนในกรณีนี้และฉันต้องการเชลล์: ${did:=nothing, gracefully}.

มันช่วยให้คุณทำสิ่งนี้:

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

ที่นี่เอกสาร

และโดยวิธี - คำจำกัดความในบรรทัดของตัวแปร null หรือ unset เป็นสาเหตุที่ทำให้งานต่อไปนี้:

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

วิธีที่ดีที่สุดในการนึกถึงhere-document คือเป็นไฟล์จริงสตรีมไปยัง file-descriptor มากขึ้นหรือน้อยลงนั่นคือสิ่งที่พวกเขาเป็น แต่กระสุนที่แตกต่างกันนำพวกเขาไปใช้ต่างกันเล็กน้อย

ไม่ว่าในกรณีใดถ้าคุณไม่ได้อ้างถึง<<LIMITERคุณจะได้รับมันสตรีมและประเมินผลexpansion.ดังนั้นการประกาศตัวแปรใน a here-documentcan สามารถทำงานได้ แต่ผ่านทางexpansionที่ จำกัด ให้คุณตั้งค่าเฉพาะตัวแปรที่ยังไม่ได้ตั้งค่า ยังคงเป็นสิ่งที่เหมาะสมกับความต้องการของคุณอย่างสมบูรณ์ที่สุดเนื่องจากค่าเริ่มต้นของคุณจะถูกตั้งค่าเสมอเมื่อคุณเรียกใช้ฟังก์ชันการพิมพ์แม่แบบ

ทำไมจะไม่ล่ะ eval?

ตัวอย่างที่ฉันนำเสนอให้วิธีการที่ปลอดภัยและมีประสิทธิภาพในการยอมรับparameters.เพราะมันจัดการกับขอบเขตตัวแปรทุกตัวที่อยู่ในชุดผ่าน${parameter:=expansion}นั้นสามารถกำหนดได้จากภายนอก ดังนั้นหากคุณใส่ทั้งหมดนี้ไว้ในสคริปต์ชื่อ template_pr.sh แล้ววิ่ง:

 % RESULT=something_else template_pr.sh

คุณจะได้รับ:

ฉันไปที่บ้านแม่ของคุณและเห็น Disney on Ice

ถ้าคุณเขียนตัวอักษรคุณจะพบกับบางสิ่งบางอย่าง

ฉันไปที่สุสานและเห็น Disney on Ice

หากคุณทำคณิตศาสตร์เพื่อการแก้ไขคุณจะต้องมีอะไรบางอย่าง

ฉันไปที่สุสานและเห็น Disney on Ice

หากคุณทำคณิตศาสตร์เพื่อการแก้ไขคุณจะต้องมีอะไรบางอย่าง

สิ่งนี้จะไม่ทำงานสำหรับตัวแปรเหล่านั้นที่ตั้งไว้ในสคริปต์อย่างแท้จริงเช่น$EVENT, $ACTION,และ$one,แต่ฉันกำหนดเฉพาะสิ่งเหล่านั้นเพื่อแสดงให้เห็นถึงความแตกต่าง

ไม่ว่าในกรณีใดการยอมรับข้อมูลที่ไม่รู้จักลงในevaledคำสั่งนั้นไม่ปลอดภัยโดยเนื้อแท้ในขณะที่parameter expansionถูกออกแบบมาโดยเฉพาะให้ทำ


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") สิ่งนี้สามารถแก้ไขได้ด้วยการดัดแปลงบางอย่างหรือเพียงแค่เขียนโค้ดเทมเพลตตัวแปรแทนการเปลี่ยนเป็นแบบไดนามิก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.