Mengapa saya tidak dapat menentukan variabel lingkungan dan menggemakannya di baris perintah yang sama?

91

Pertimbangkan cuplikan ini:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

Di sini saya telah mengatur $SOMEVARke AAAbaris pertama - dan ketika saya menggemakannya di baris kedua, saya mendapatkan AAAisinya seperti yang diharapkan.

Tetapi kemudian, jika saya mencoba menentukan variabel pada baris perintah yang sama dengan echo:

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

... Saya tidak mendapatkan BBBseperti yang saya harapkan - saya mendapatkan nilai lama ( AAA).

Apakah seharusnya seperti ini? Jika demikian, mengapa Anda dapat menentukan variabel like LD_PRELOAD=/... program args ...dan membuatnya berfungsi? Apa yang saya lewatkan?

sdaau
sumber
2
Ini berfungsi saat Anda membuat tugas sebagai pernyataan terpisah, atau saat menjalankan skrip dengan lingkungannya sendiri, tetapi tidak saat memulai perintah di lingkungan saat ini. Menarik!
Todd A. Jacobs
1
Alasan LD_PRELOADkarya adalah bahwa variabel diatur dalam program lingkungan - tidak dalam baris perintah.
Dijeda sampai pemberitahuan lebih lanjut.

Jawaban:

103

Apa yang Anda lihat adalah perilaku yang diharapkan. Masalahnya adalah bahwa shell induk mengevaluasi $SOMEVARpada baris perintah sebelum memanggil perintah dengan lingkungan yang dimodifikasi. Anda perlu mendapatkan evaluasi $SOMEVARditangguhkan sampai lingkungan ditetapkan.

Pilihan langsung Anda meliputi:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

Keduanya menggunakan tanda kutip tunggal untuk mencegah shell induk mengevaluasi $SOMEVAR; itu hanya dievaluasi setelah disetel di lingkungan (sementara, selama durasi perintah tunggal).

Pilihan lain adalah dengan menggunakan notasi sub-shell (seperti yang juga disarankan oleh Marcus Kuhn dalam jawabannya ):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

Variabel diatur hanya di sub-shell

Jonathan Leffler
sumber
Luar biasa, @JonathanLeffler - terima kasih banyak atas penjelasannya; Bersulang!
sdaau
Penambahan dari @ markus-kuhn sulit untuk ditaksir terlalu tinggi.
Alex Che
37

Masalahnya, Ditinjau Kembali

Sejujurnya, manual ini membingungkan dalam hal ini. The pengguna GNU Bash mengatakan:

Lingkungan untuk setiap perintah atau fungsi sederhana [perhatikan bahwa ini tidak termasuk bawaan] dapat ditambah sementara dengan mengawalnya dengan penetapan parameter, seperti yang dijelaskan dalam Parameter Shell. Pernyataan penugasan ini hanya mempengaruhi lingkungan yang terlihat oleh perintah itu.

Jika Anda benar-benar mengurai kalimat tersebut, maksudnya adalah lingkungan untuk perintah / fungsi diubah, tetapi bukan lingkungan untuk proses induk. Jadi, ini akan berhasil:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

karena lingkungan untuk perintah env telah dimodifikasi sebelum dijalankan. Namun, ini tidak akan berhasil:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

karena ketika ekspansi parameter dilakukan oleh shell.

Langkah Penerjemah

Bagian lain dari masalah ini adalah Bash menentukan langkah-langkah berikut untuk penerjemahnya:

  1. Membaca masukannya dari file (lihat Skrip Shell), dari string yang disediakan sebagai argumen ke opsi pemanggilan -c (lihat Memanggil Bash), atau dari terminal pengguna.
  2. Memecah input menjadi kata-kata dan operator, mematuhi aturan kutipan yang dijelaskan di Quoting. Token ini dipisahkan oleh karakter meta. Ekspansi alias dilakukan dengan langkah ini (lihat Alias).
  3. Mengurai token menjadi perintah sederhana dan gabungan (lihat Perintah Shell).
  4. Melakukan berbagai ekspansi shell (lihat Ekspansi Shell), memecah token yang diperluas menjadi daftar nama file (lihat Perluasan Nama File) serta perintah dan argumen.
  5. Melakukan pengalihan yang diperlukan (lihat Pengalihan) dan menghapus operator pengalihan dan operannya dari daftar argumen.
  6. Jalankan perintah (lihat Perintah Pelaksana).
  7. Secara opsional menunggu perintah selesai dan mengumpulkan status keluarnya (lihat Status Keluar).

Apa yang terjadi di sini adalah bahwa bawaan tidak mendapatkan lingkungan eksekusinya sendiri, jadi mereka tidak pernah melihat lingkungan yang dimodifikasi. Selain itu, perintah-perintah sederhana (misalnya / bin / echo) yang mendapatkan ennvironment dimodifikasi (yang mengapa contoh env bekerja) tetapi ekspansi shell berlangsung di saat lingkungan pada langkah # 4.

Dengan kata lain, Anda tidak mengirimkan 'aaa $ TESTVAR ccc' ke / bin / echo; Anda meneruskan string yang diinterpolasi (seperti yang diperluas di lingkungan saat ini) ke / bin / echo. Dalam kasus ini, karena lingkungan saat ini tidak memiliki TESTVAR , Anda cukup meneruskan 'aaa ccc' ke perintah.

Ringkasan

Dokumentasinya bisa jauh lebih jelas. Untung ada Stack Overflow!

Lihat juga

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment

Todd A. Jacobs
sumber
Saya telah memberikan suara positif ini - tetapi saya baru saja kembali ke pertanyaan ini, dan posting ini berisi petunjuk yang saya butuhkan; terima kasih banyak, @CodeGnome!
sdaau
Saya tidak tahu apakah Bash telah berubah di daerah ini sejak jawaban ini telah diposting, tetapi diawali tugas variabel melakukan pekerjaan dengan builtin sekarang. Misalnya FOO=foo eval 'echo $FOO'mencetak fooseperti yang diharapkan. Artinya, Anda dapat melakukan hal-hal seperti IFS="..." read ....
Will Vousden
Saya pikir apa yang terjadi adalah bahwa Bash sebenarnya memodifikasi lingkungannya sendiri untuk sementara, dan memulihkannya setelah perintah selesai, yang dapat memiliki efek samping yang aneh.
Will Vousden
22

Untuk mencapai apa yang Anda inginkan, gunakan

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

Alasan:

  • Anda harus memisahkan penetapan dengan titik koma atau baris baru dari perintah berikutnya, jika tidak maka tidak akan dijalankan sebelum perluasan parameter terjadi untuk perintah berikutnya (gema).

  • Anda perlu membuat tugas di dalam lingkungan subkulit , untuk memastikannya tidak bertahan di luar baris saat ini.

Solusi ini lebih pendek, lebih rapi dan lebih efisien daripada beberapa solusi lain yang disarankan, khususnya ini tidak menciptakan proses baru.

Markus Kuhn
sumber
3
Untuk calon karyawan Google yang berakhir di sini: Ini mungkin jawaban terbaik untuk pertanyaan ini. Untuk memperumitnya lebih jauh, jika Anda membutuhkan tugas untuk tersedia di lingkungan perintah, Anda perlu mengekspornya. Subkulit masih mencegah tugas tetap ada. (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')")
eaj
@eaj Untuk mengekspor variabel shell ke satu panggilan program eksternal, seperti dalam contoh Anda, cukup gunakanSOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"
Markus Kuhn
10

Alasannya adalah ini menetapkan variabel lingkungan untuk satu baris. Tapi, echojangan lakukan ekspansi, bashya. Oleh karena itu, variabel Anda sebenarnya diperluas sebelum perintah dijalankan, meskipun SOME_VARadalah BBBdalam konteks perintah echo.

Untuk melihat efeknya, Anda dapat melakukan sesuatu seperti:

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

Di sini variabel tidak diperluas sampai proses anak dijalankan, sehingga Anda melihat nilai yang diperbarui. jika Anda memeriksa SOME_VARIABLElagi di shell induk, itu masih AAAseperti yang diharapkan.

Kesalahan fatal
sumber
1
1 untuk penjelasan yang benar tentang mengapa itu tidak berfungsi seperti yang tertulis, dan untuk solusi yang layak.
Jonathan Leffler
1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

Gunakan ; untuk memisahkan pernyataan yang ada di baris yang sama.

Kyros
sumber
1
Itu berhasil, tetapi bukan itu intinya. Idenya adalah mengatur lingkungan hanya untuk satu perintah, tidak secara permanen seperti solusi Anda.
Jonathan Leffler
Terima kasih untuk @Kyros itu; tidak tahu kenapa saya melewatkannya sekarang :) Masih mengembara bagaimana LD_PRELOADdan seperti itu dapat bekerja di depan eksekusi tanpa titik koma, meskipun ... Banyak terima kasih lagi - tepuk tangan!
sdaau
@JonathanLeffler - memang, itulah idenya; Saya tidak menyadari titik koma membuat perubahan menjadi permanen - terima kasih telah memperhatikannya!
sdaau
1

Berikut salah satu alternatifnya:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz
brian
sumber
Apakah Anda menggunakan &&atau ;untuk memisahkan perintah, tugas tetap ada, yang bukan merupakan perilaku OP yang diinginkan. Markus Kuhn memiliki versi yang benar dari jawaban ini.
eaj