Cara menunda ekspansi variabel

18

Saya ingin menginisialisasi beberapa string di bagian atas skrip saya dengan variabel yang belum ditetapkan, seperti:

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

dan kemudian nanti PLACE, EVENT, ACTION, dan RESULTakan ditetapkan. Saya ingin kemudian dapat mencetak string saya dengan variabel yang diperluas. Apakah satu-satunya pilihan saya eval? Ini sepertinya berhasil:

eval "echo ${str1}"

apakah standar ini? apakah ada cara yang lebih baik untuk melakukan ini? Akan lebih baik untuk tidak menjalankan evalmengingat variabel bisa menjadi apa saja.

Harun
sumber

Jawaban:

23

Dengan jenis input yang Anda perlihatkan, satu-satunya cara untuk memanfaatkan ekspansi shell untuk mengganti nilai menjadi string adalah dengan menggunakannya evaldalam beberapa bentuk. Ini aman selama Anda mengontrol nilai str1dan dapat memastikan bahwa itu hanya mereferensikan variabel yang dikenal sebagai aman (tidak mengandung data rahasia) dan tidak mengandung karakter khusus shell lain yang tidak dikutip. Anda harus memperluas string di dalam tanda kutip ganda atau dalam dokumen di sini, dengan cara "$\`itu khusus (mereka harus didahului oleh \in str1).

eval "substituted=\"$str1\""

Akan jauh lebih kuat untuk mendefinisikan suatu fungsi daripada string.

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

Atur variabel lalu panggil fungsi fill_templateuntuk mengatur variabel output.

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 'SANGAT berhenti menjadi jahat'
sumber
2
Kerja bagus menggunakan fungsi untuk menunda evaluasi, dan untuk menghindari panggilan eval eksplisit.
Clayton Stanley
Solusi yang bagus, ini banyak membantu saya. Terima kasih!
Stuart
8

Ketika saya mengerti maksud Anda, saya tidak percaya bahwa salah satu dari jawaban ini benar. evaltidak perlu dengan cara apa pun, Anda juga tidak perlu bahkan dua kali mengevaluasi variabel Anda.

Memang benar, @Gilles sangat dekat, tetapi dia tidak membahas masalah kemungkinan nilai-nilai utama dan bagaimana mereka harus digunakan jika Anda membutuhkannya lebih dari sekali. Lagi pula, templat harus digunakan lebih dari sekali, bukan?

Saya pikir ini lebih merupakan urutan di mana Anda mengevaluasi mereka yang penting. Pertimbangkan yang berikut ini:

TERATAS

Di sini Anda akan menetapkan beberapa default dan bersiap untuk mencetaknya saat dipanggil ...

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

TENGAH

Di sinilah Anda mendefinisikan fungsi lain untuk memanggil fungsi cetak Anda berdasarkan hasil mereka ...

    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
    }

BAWAH

Anda telah menyiapkan semuanya sekarang, jadi di sinilah Anda akan menjalankan dan menarik hasil Anda.

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

HASIL

Saya akan membahas mengapa suatu saat, tetapi menjalankan di atas menghasilkan hasil sebagai berikut:

_less_important_function()'s Lari pertama:

Saya pergi ke rumah ibumu dan melihat Disney on Ice.

Jika Anda melakukan kaligrafi, Anda akan berhasil.

kemudian _more_important_function():

Saya pergi ke kuburan dan melihat Disney on Ice.

Jika Anda melakukan matematika perbaikan Anda akan berhasil.

_less_important_function() lagi:

Saya pergi ke kuburan dan melihat Disney on Ice.

Jika Anda melakukan matematika perbaikan Anda akan menyesalinya.

BAGAIMANA ITU BEKERJA:

Fitur utama di sini adalah konsep conditional ${parameter} expansion.Anda dapat mengatur variabel ke nilai hanya jika tidak disetel atau null menggunakan formulir:

${var_name: =desired_value}

Jika Anda hanya ingin menetapkan variabel yang tidak disetel, Anda akan menghilangkan :colondan nilai-nilai nol akan tetap apa adanya.

TENTANG RUANG LINGKUP:

Anda mungkin memperhatikan bahwa dalam contoh di atas $PLACEdan $RESULTdiubah ketika diatur melalui parameter expansionmeskipun _top_of_script_pr()telah dipanggil, mungkin pengaturan mereka ketika dijalankan. Alasan karya ini adalah bahwa _top_of_script_pr()adalah ( subshelled )fungsi - Saya sertakan dalam parensdaripada { curly braces }yang digunakan untuk orang lain. Karena itu disebut dalam subkulit, setiap variabel yang disetel adalah locally scopeddan saat ia kembali ke shell induknya, nilai-nilai itu menghilang.

Tetapi ketika _more_important_function()set $ACTIONitu globally scopedbegitu mempengaruhi _less_important_function()'sevaluasi kedua $ACTIONkarena _less_important_function()set $ACTIONhanya melalui${parameter:=expansion}.

:BATAL

Dan mengapa saya menggunakan :colon?Yah terkemuka , manhalaman akan memberitahu Anda bahwa : does nothing, gracefully.Anda lihat, parameter expansionpersis seperti apa kedengarannya - itu expandsdengan nilai ${parameter}.Jadi ketika kita mengatur variabel dengan ${parameter:=expansion}kita pergi dengan nilainya - yang shell akan upaya untuk mengeksekusi in-line. Jika ia mencoba menjalankannya, the cemeteryia hanya akan meludahkan beberapa kesalahan pada Anda. PLACE="${PLACE:="the cemetery"}"akan menghasilkan hasil yang sama, tetapi juga berlebihan dalam hal ini dan saya lebih suka shell: ${did:=nothing, gracefully}.

Itu memungkinkan Anda untuk melakukan ini:

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

DI SINI-DOKUMEN

Dan omong-omong - definisi in-line dari variabel nol atau tidak disetel juga mengapa yang berikut ini berfungsi:

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

Cara terbaik untuk memikirkan here-document adalah sebagai file aktual yang dialirkan ke file-deskriptor input. Kurang lebih seperti itu, tetapi cangkang yang berbeda menerapkannya sedikit berbeda.

Dalam kasus apa pun, jika Anda tidak mengutip, <<LIMITERAnda dapat mengalirkannya dan mengevaluasi untuk expansion.Jadi mendeklarasikan variabel dalam sebuah here-documentcan dapat berfungsi, tetapi hanya melalui expansionyang membatasi Anda untuk menetapkan hanya variabel yang belum ditetapkan. Namun, itu sangat sesuai dengan kebutuhan Anda seperti yang telah Anda gambarkan, karena nilai default Anda akan selalu ditetapkan ketika Anda memanggil fungsi cetak template Anda.

KENAPA TIDAK eval?

Nah, contoh yang saya sajikan memberikan cara yang aman dan efektif untuk menerima parameters.Karena menangani ruang lingkup, setiap variabel dalam set via ${parameter:=expansion}dapat didefinisikan dari luar. Jadi, jika Anda meletakkan semua ini dalam skrip yang disebut template_pr.sh dan jalankan:

 % RESULT=something_else template_pr.sh

Anda akan mendapatkan:

Saya pergi ke rumah ibumu dan melihat Disney on Ice

Jika Anda melakukan kaligrafi, Anda akan melakukan sesuatu

Saya pergi ke kuburan dan melihat Disney on Ice

Jika Anda melakukan perbaikan matematika Anda akan something_else

Saya pergi ke kuburan dan melihat Disney on Ice

Jika Anda melakukan perbaikan matematika Anda akan something_else

Ini tidak akan berfungsi untuk variabel-variabel yang secara harfiah diatur dalam skrip, seperti $EVENT, $ACTION,dan$one, tetapi saya hanya mendefinisikan mereka dengan cara itu untuk menunjukkan perbedaannya.

Dalam kasus apa pun, penerimaan input yang tidak diketahui ke dalam suatu evaledpernyataan pada dasarnya tidak aman, sedangkan parameter expansionsecara khusus dirancang untuk melakukannya.

mikeserv
sumber
1

Anda bisa menggunakan placeholder untuk templat string alih-alih variabel yang tidak diperluas. Ini akan cepat berantakan. Jika apa yang Anda lakukan sangat template berat, Anda mungkin ingin mempertimbangkan bahasa dengan pustaka template nyata.

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"

Kelemahan dari hal di atas adalah bahwa variabel template harus berupa kata sendiri (mis. Anda tidak dapat melakukannya "%prefix%foo"). Ini bisa diperbaiki dengan beberapa modifikasi, atau hanya dengan mengkodekan variabel templat alih-alih dinamis.

jordanm
sumber