Penempatan variabel memengaruhi shell yang sedang berjalan

8

Saat menulis beberapa kode saya menemukan bahwa baris ini:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015

Benar memberikan waktu aktual di "Los Angeles" dan bahwa nilai variabel TZtidak dipertahankan. Semua seperti yang diharapkan.

Namun, dengan baris ini, yang saya gunakan untuk memperluas beberapa format hingga saat ini, dan yang pada dasarnya mengeksekusi hal yang sama, mempertahankan nilai TZ:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles

Setelah beberapa tes lagi, saya mengetahui bahwa ini hanya terjadi pada beberapa shell. Ini terjadi di dash, ksh tetapi tidak di bash atau zsh.

Q's

Pertanyaannya adalah:

  • Mengapa nilai TZ dipertahankan dalam shell ini?
  • Bagaimana itu bisa dihindari / dikendalikan (jika mungkin)?

Tambahan.

Saya menjalankan beberapa tes dengan dua baris ini:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"

Dan ini menghasilkan:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 

Nilai TZ memengaruhi running shell di semua shell kecuali bash dan zsh.


sumber

Jawaban:

6

Seperti yang Anda temukan, itu perilaku khusus. Tapi itu juga masuk akal.

Nilai dipertahankan di lingkungan shell karena alasan yang sama nilai variabel lingkungan lainnya dipertahankan oleh perintah lain ketika Anda awali definisi ke baris perintah mereka - Anda sedang mengatur variabel di lingkungan mereka.

The builtin khusus umumnya paling intrinsik variasi dalam shell apapun - evalpada dasarnya adalah sebuah nama diakses untuk parser shell, settrek dan mengkonfigurasi shell pilihan dan parameter shell, return/ break/ continuealiran kontrol loop pemicu, trapmenangani sinyal, execmembuka / menutup file. Ini semua adalah utilitas mendasar - dan biasanya diimplementasikan dengan pembungkus yang hampir tidak ada di atas daging dan kentang cangkang Anda.

Menjalankan sebagian besar perintah melibatkan beberapa lingkungan berlapis - lingkungan subkulit (yang tidak harus menjadi proses terpisah) - yang tidak Anda dapatkan saat memanggil builtin khusus. Jadi ketika Anda mengatur lingkungan untuk salah satu dari perintah ini Anda mengatur lingkungan untuk shell Anda. Karena pada dasarnya mereka mewakili shell Anda.

Tapi itu bukan satu-satunya perintah yang mempertahankan lingkungan seperti itu - fungsinya juga sama. Dan kesalahan berperilaku berbeda untuk built-in khusus - coba cat <doesntexistdan kemudian coba exec <doesntexistatau bahkan hanya : <doesntexistdan sementara catperintah akan mengeluh, execatau :akan membunuh shell POSIX. Hal yang sama berlaku untuk kesalahan ekspansi pada baris perintah mereka. Mereka lingkaran utama , pada dasarnya.

Perintah-perintah ini tidak harus mempertahankan lingkungan - beberapa shell membungkus internal mereka lebih erat daripada yang lain, mengekspos lebih sedikit fungsionalitas inti dan menambahkan lebih banyak buffer antara programmer dan antarmuka. Kerang yang sama ini mungkin juga cenderung sedikit lebih lambat dari yang lain. Jelas mereka membutuhkan banyak penyesuaian non-standar untuk membuatnya agar sesuai dengan spesifikasi. Lagi pula, ini bukan hal yang buruk :

fn(){ bad_command || return=$some_value return; }

Itu hal yang mudah . Bagaimana lagi Anda akan mempertahankan kembalinya bad_commandbegitu saja tanpa harus mengatur banyak lingkungan tambahan dan masih melakukan penugasan bersyarat?

arg=$1 shift; x=$y unset y

Hal-hal semacam itu juga berhasil. Di tempat swap lebih sederhana.

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"

+x+y+z x y z

...atau...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"

... adalah satu lagi yang saya suka gunakan ...

echo kill my computer; haha! just kidding!
mikeserv
sumber
@ Binzebra - tetapi intinya adalah mereka tidak bekerja secara berbeda - ketika Anda menetapkan variabel untuk beberapa perintah lain mereka bertahan di lingkungan yang dapat dieksekusi lainnya. ketika Anda mengatur variabel di lingkungan shell Anda, mereka juga bertahan.
mikeserv
3

Ternyata ada alasan yang sangat spesifik untuk perilaku ini.
Deskripsi tentang apa yang terjadi sedikit lebih lama.

Hanya penugasan.

Baris perintah yang dibuat (hanya) dari penugasan akan mengatur variabel untuk shell ini .

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>

Nilai vars yang ditugaskan akan dipertahankan.

Perintah eksternal.

Tugas sebelum variabel set perintah eksternal untuk itu shell hanya:

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>

Dan maksud saya "eksternal" sebagai perintah yang harus dicari di PATH.

Ini juga berlaku untuk built-in normal (seperti cd, misalnya):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>

Sampai disini semua seperti yang biasanya diharapkan.

Built-In Khusus.

Tetapi untuk built-in khusus, POSIX mensyaratkan bahwa nilai ditetapkan untuk shell ini .

  1. Tugas variabel yang ditentukan dengan utilitas internal tetap berlaku setelah built-in selesai.
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>

Saya menggunakan panggilan untuk shmenganggap itu shadalah shell yang sesuai dengan POSIX.

Ini bukan sesuatu yang biasanya digunakan.

Ini berarti bahwa penugasan ditempatkan di depan daftar built-in khusus ini akan mempertahankan nilai yang ditetapkan dalam shell yang sedang berjalan:

break : continue . eval exec exit export 
readonly return set shift times trap unset

Ini akan terjadi jika shell berfungsi sesuai dengan spesifikasi POSIX.

Kesimpulan:

Dimungkinkan untuk mengatur variabel hanya untuk satu perintah, perintah apa saja, dengan memastikan bahwa perintah tersebut bukan built-in khusus. Perintahnya commandadalah builtin reguler. Ia hanya memberi tahu shell untuk menggunakan perintah, bukan fungsi. Baris ini bekerja di semua shell (kecuali ksh93):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>

Dalam hal demikian vars a dan b diatur untuk lingkungan perintah perintah, dan dibuang setelah itu.

Sebagai gantinya, ini akan mempertahankan nilai yang ditetapkan (kecuali bash dan zsh):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>

Perhatikan bahwa tugas setelah eval adalah tanda kutip tunggal untuk melindunginya dari ekspansi yang tidak diinginkan.

Jadi: Untuk menempatkan variabel dalam lingkungan perintah, gunakan command eval:


sumber