Keluar dari variabel untuk digunakan sebagai konten skrip lain

20

Pertanyaan ini bukan tentang bagaimana menulis string literal yang benar. Saya tidak dapat menemukan pertanyaan terkait yang bukan tentang cara keluar dari variabel untuk konsumsi langsung dalam skrip atau oleh program lain.

Tujuan saya adalah mengaktifkan skrip untuk menghasilkan skrip lain. Ini karena tugas dalam skrip yang dihasilkan akan berjalan di mana saja dari 0 hingga n kali di komputer lain, dan data yang darinya dihasilkan dapat berubah sebelum dijalankan (lagi), jadi melakukan operasi secara langsung, melalui jaringan akan tidak bekerja.

Diberikan variabel yang diketahui yang mungkin berisi karakter khusus seperti tanda kutip tunggal, saya perlu menuliskannya sebagai string literal yang sepenuhnya diloloskan, misalnya variabel yang foomengandung bar'bazharus muncul dalam skrip yang dihasilkan sebagai:

qux='bar'\''baz'

yang akan ditulis dengan menambahkan "qux=$foo_esc"ke baris script lainnya. Saya melakukannya menggunakan Perl seperti ini:

foo_esc="'`perl -pe 's/('\'')/\\1\\\\\\1\\1/g' <<<"$foo"`'"

tapi ini sepertinya berlebihan.

Saya tidak berhasil melakukannya dengan bash sendirian. Saya telah mencoba banyak variasi:

foo_esc="'${file//\'/\'\\\'\'}'"
foo_esc="'${file//\'/'\\''}'"

tetapi salah satu garis miring muncul di output (ketika saya lakukan echo "$foo"), atau mereka menyebabkan kesalahan sintaks (mengharapkan input lebih lanjut jika dilakukan dari shell).

Walf
sumber
aliasdan / atau setsecara universal
mikeserv
@ mikeserv Maaf, saya tidak yakin apa yang Anda maksud.
Walf
alias "varname=$varname" varnameatauvar=value set
mikeserv
2
@ mikeserv Konteks itu tidak cukup bagi saya untuk memahami apa yang seharusnya dilakukan saran Anda, atau bagaimana saya akan menggunakannya sebagai metode generik untuk menghindari variabel apa pun. Ini masalah terpecahkan, sobat.
Walf

Jawaban:

26

Bash memiliki opsi ekspansi parameter untuk kasus ini :

${parameter@Q}Ekspansi adalah string yang merupakan nilai parameter yang dikutip dalam format yang dapat digunakan kembali sebagai input.

Jadi dalam hal ini:

foo_esc="${foo@Q}"

Ini didukung di Bash 4.4 dan lebih tinggi. Ada beberapa opsi untuk bentuk ekspansi lain juga, dan untuk secara khusus menghasilkan pernyataan penugasan lengkap ( @A).

Michael Homer
sumber
7
Rapi, tetapi hanya memiliki 4,2 yang memberi bad substitution.
Walf
3
Setara shell Z adalah "${foo:q}".
JdeBP
Benar-benar menyelamatkan hidupku! "${foo@Q}"bekerja!
hao
@ JdeBP yang setara dengan Z shell tidak berfungsi. Ada ide lain untuk zsh?
Steven Shaw
1
Saya menemukan jawabannya: "$ {(@ qq) foo}"
Steven Shaw
10

Bash menyediakan printfpenentu %qformat bawaan, yang menjalankan shell untuk Anda, bahkan di versi Bash yang lebih lama (<4.0):

printf '[%q]\n' "Ne'er do well"
# Prints [Ne\'er\ do\ well]

printf '[%q]\n' 'Sneaky injection $( whoami ) `ls /root`'
# Prints [Sneaky\ injection\ \$\(\ whoami\ \)\ \`ls\ /root\`]

Trik ini juga dapat digunakan untuk mengembalikan array data dari suatu fungsi:

function getData()
{
  printf '%q ' "He'll say hi" 'or `whoami`' 'and then $( byebye )'
}

declare -a DATA="( $( getData ) )"
printf 'DATA: [%q]\n' "${DATA[@]}"
# Prints:
# DATA: [He\'ll\ say\ hi]
# DATA: [or\ \`whoami\`]
# DATA: [and\ then\ \$\(\ byebye\ \)]

Perhatikan bahwa Bash printfbuiltin berbeda dari printfutilitas yang dibundel dengan sebagian besar sistem operasi mirip Unix. Jika, karena alasan tertentu, printfperintah memanggil utilitas alih-alih builtin, Anda selalu dapat menjalankannya builtin printf.

Dejay Clayton
sumber
Saya tidak yakin bagaimana itu membantu jika apa yang saya perlukan akan dicetak'Ne'\''er do well' , dll., Yaitu kutipan yang termasuk dalam output.
Walf
1
@ Ketika saya pikir Anda tidak mengerti bahwa kedua bentuk itu setara, dan keduanya sama amannya satu sama lain. Eg [[ 'Ne'\''er do well' == Ne\'er\ do\ well ]] && echo 'equivalent!'will echoequivalent!
Dejay Clayton
Saya memang merindukan itu: P namun saya lebih suka bentuk yang dikutip karena lebih mudah dibaca dalam penampil / editor yang menyoroti sintaksis.
Walf
@Walf sepertinya pendekatan Anda cukup berbahaya, mengingat dalam contoh Anda Perl, melewatkan nilai seperti 'hello'menghasilkan nilai yang salah ''\''hello'', yang memiliki string kosong terkemuka yang tidak perlu (dua tanda kutip tunggal pertama), dan tanda kutip tunggal trailing yang tidak tepat.
Dejay Clayton
1
@Walf, untuk klarifikasi, $'escape-these-chars'adalah fitur mengutip ANSI-C dari Bash yang menyebabkan semua karakter dalam string yang ditentukan untuk melarikan diri. Dengan demikian, untuk dengan mudah membuat string literal yang berisi baris baru di dalam nama file (misalnya $'first-line\nsecond-line'), gunakan \ndalam konstruk ini.
Dejay Clayton
8

Saya kira saya tidak RTFM. Itu bisa dilakukan seperti ini:

q_mid=\'\\\'\'
foo_esc="'${foo//\'/$q_mid}'"

Lalu echo "$foo_esc"berikan yang diharapkan'bar'\''baz'


Bagaimana saya sebenarnya menggunakannya dengan fungsi:

function esc_var {
    local mid_q=\'\\\'\'
    printf '%s' "'${1//\'/$mid_q}'"
}

...

foo_esc="`esc_var "$foo"`"

Mengubah ini untuk menggunakan printfbuilt-in dari solusi Dejay:

function esc_vars {
    printf '%q' "$@"
}
Walf
sumber
3
Saya akan menggunakan solusi @ michael-homer jika versi saya mendukungnya.
Walf
4

Ada beberapa solusi untuk mengutip nilai var:

  1. alias
    Dalam kebanyakan shells (di mana alias tersedia) (kecuali csh, tcsh dan mungkin yang lain suka csh):

    $ alias qux=bar\'baz
    $ alias qux
    qux='bar'\''baz'

    Ya, ini bekerja di banyak shcangkang seperti dasbor atau abu.

  2. atur
    Juga di sebagian besar shell (sekali lagi, bukan csh):

    $ qux=bar\'baz
    $ set | grep '^qux='
    qux='bar'\''baz'
  3. mengeset
    dalam beberapa shell (setidaknya ksh, bash dan zsh):

    $ qux=bar\'baz
    $ typeset -p qux
    typeset qux='bar'\''baz'             # this is zsh, quoting style may
                                         # be different for other shells.
  4. ekspor
    Pertama lakukan:

    export qux=bar\'baz

    Kemudian gunakan:
    export -p | grep 'qux=' export -p | grep 'qux='
    export -p qux

  5. kutipan
    echo "${qux@Q}"
    echo "${(qq)qux}" # dari satu hingga empat q dapat digunakan.

Ishak
sumber
Pendekatan alias pintar dan sepertinya ditentukan oleh POSIX. Untuk portabilitas maksimum, saya rasa inilah caranya. Saya percaya saran yang melibatkan grepdengan exportatau setbisa pecah pada variabel yang berisi baris tertanam.
jw013