Mengapa “A = 10 echo $ A” tidak mencetak 10?

25

Perintah ini:

A=10 echo $A

mencetak garis kosong. Mengapa tidak 10? Mengapa pengaturan lingkungan sementara di tempat tidak berfungsi?

Saya ingin tahu alasan dan penjelasan daripada solusi.

Saya menggunakan

LANG=C gcc ...

untuk memaksa gcc gunakan bahasa fallback (Inggris) alih-alih bahasa sistem (Cina). Jadi saya menganggap VAR=valueawalan akan mengatur lingkungan sementara untuk perintah yang mengikutinya. Tetapi sepertinya saya memiliki beberapa kesalahpahaman.

Mesin Tanah
sumber

Jawaban:

22

Ini masalah urutan di mana berbagai langkah mengevaluasi suatu perintah terjadi.

A=10 echo $Apertama mengurai perintah menjadi perintah sederhana yang terbuat dari tiga kata A=10, echodan $A. Kemudian setiap kata mengalami substitusi variabel, yaitu transformasi ekspansi variabel seperti $Amenjadi nilai-nilai mereka (saya menghilangkan langkah-langkah yang tidak terlihat).

Jika Amemiliki nilai fooawalnya, hasil dari langkah-langkah ekspansi adalah perintah sederhana yang masih memiliki tiga kata: A=10, echodan foo. (Shell juga mengingat pada titik ini karakter mana yang semula berada di dalam tanda kutip - dalam hal ini, tidak ada.) Langkah selanjutnya adalah menjalankan perintah. Karena A=10dimulai dengan nama variabel yang valid diikuti dengan tanda sama dengan, itu diperlakukan sebagai tugas; variabel Adiatur ke 10dalam shell dan lingkungan selama eksekusi perintah. (Biasanya Anda harus menulis export Aagar ada Adi lingkungan dan bukan hanya sebagai variabel shell; ini merupakan pengecualian.) Kata berikutnya bukan tugas, jadi itu diperlakukan sebagai nama perintah (itu adalah perintah bawaan). Ituechoperintah tidak tergantung pada variabel apa pun, sehingga A=10 echo $Amemiliki efek yang persis sama dengan echo $A.

Jika Anda ingin menetapkan variabel hanya untuk durasi perintah, tetapi dengan mempertimbangkan tugas saat menjalankan perintah, Anda dapat menggunakan subkulit. Subkulit, ditunjukkan oleh tanda kurung, membuat semua perubahan status (penugasan variabel, direktori saat ini, definisi fungsi, dll.) Lokal ke subkulit.

(A=10; echo $A)

Lakukan export A=10jika Anda ingin mengekspor variabel ke lingkungan sehingga dilihat oleh program eksternal.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Terima kasih, bisakah saya katakan A=10 (echo $A)dan dapatkan 10?
Mesin Bumi
2
@EarthEngine Tidak, itu akan menjadi kesalahan sintaksis. Penugasan harus pada awal perintah sederhana (yaitu hanya nama perintah dan beberapa parameter, dan opsional beberapa penugasan awal dan beberapa pengalihan). A=10; (echo $A)output 10tetapi juga ditetapkan Auntuk sisa skrip.
Gilles 'SANGAT berhenti menjadi jahat'
2
@EarthEngine Tapi bisa Anda katakan A=10 eval 'echo $A'. Kutipan tunggal berhenti $Aditafsirkan sampai seluruh baris dievaluasi ... Pada saat itu A = 10. Saya menganggap jawaban ini lebih benar daripada yang diterima.
Oli
Saya pikir ini adalah penjelasan yang benar. Alasan untuk perilaku itu hanya urutan ketika ekspansi $Adan penugasan Asedang terjadi. Misalnya A=5; A=6 let 'a=A'; echo $amengembalikan 6tidak 5dan saya tidak berpikir letmemulai subkulit, karena itu adalah perintah bawaan.
David Ongaro
@EarthEngine: Ketika itu mengatakan bahwa penjelasan yang benar adalah urutan evaluasi, mungkin menyesatkan: A=10 echo $Aakan tidak mengatur A=10untuk perintah setiap setelah itu, bahkan jika mereka berada di baris yang berbeda (ketika jelas tugas itu sudah dievaluasi). Ini bukan tentang ketertiban, ini tentang ruang lingkup
MestreLion
37

Bila Anda menggunakan LANG=C gcc ... apa yang terjadi adalah bahwa shell menetapkan LANG untuk gcclingkungan 's hanya , dan tidak untuk saat ini lingkungan itu sendiri ( lihat catatan ). Jadi setelah gccselesai, LANGkembali ke nilai sebelumnya (atau tidak disetel).

Selain itu, ketika Anda menggunakannya A=10 echo $Aadalah shell yang menggantikan $ A, bukan echo, dan substitusi ini (disebut "ekspansi") terjadi sebelum pernyataan dievaluasi (termasuk penugasan), sehingga untuk bekerja sesuai Adengan nilai yang diharapkan , nilai harus sudah ditetapkan. di lingkungan saat ini sebelum pernyataan itu.

Itu sebabnya A=10 echo $Atidak berfungsi seperti yang diharapkan: A=10akan ditetapkan untuk gema, tetapi gema secara internal mengabaikan nilai variabel lingkungan A. Dan $Adiganti dengan nilai yang ditetapkan di shell saat ini (yang tidak ada), dan kemudian diteruskan sebagai argumen untuk echo.

Jadi asumsi Anda benar: VAR=value command tidak berfungsi, tetapi ini hanya relevan jika secara commandinternal menggunakan VAR. Jika tidak, Anda masih dapat melewati valuesebagai argumen untuk command, tetapi argumen digantikan oleh arus shell, sehingga mereka harus ditetapkan sebelum penggunaan:VAR=value; command "$VAR"

Jika Anda tahu cara membuat skrip yang dapat dieksekusi, Anda dapat mencoba ini sebagai ujian:

#!/bin/sh
echo "1st argument is $1"
echo "A is $A"

Simpan sebagai testscriptdan coba:

$ A=5; A=10 testscript "$A"; echo "$A"
1st argument is 5
A is 10
5

Terakhir namun tidak kalah pentingnya, ada baiknya mengetahui perbedaan antara variabel shell dan lingkungan dan argumen program .

Berikut ini beberapa referensi bagus:

.

(*) Catatan: teknis shell tidak diatur dalam lingkungan saat ini juga, dan inilah sebabnya: Beberapa perintah, seperti echo, readdan testyang builtin shell , dan dengan demikian mereka tidak menelurkan sebuah proses anak. Mereka berjalan di lingkungan saat ini. Tetapi shell menangani tugas hanya berlangsung sampai perintah berjalan, jadi untuk semua tujuan praktis efeknya sama: tugas hanya dilihat oleh perintah tunggal.

MestreLion
sumber
2
Penjelasan ini sebenarnya salah, meskipun menghasilkan kesimpulan yang benar di semua kecuali beberapa kasus sudut. Penjelasan sebenarnya adalah urutan ekspansi: $Adievaluasi sebelum penugasan berlangsung. Saya pikir penjelasan Anda hanya gagal dalam kasus utilitas built-in reguler yang perilakunya tergantung pada nilai variabel: built-in tidak melihat nilai yang diberikan. Contoh umum adalah IFS=: read one two three rest, yang membaca bidang yang dipisahkan dengan titik dua: readbuiltin memang melihat nilai IFS.
Gilles 'SANGAT berhenti menjadi jahat'
“Not for the current shell sendiri” salah: variabel diatur dalam shell saat ini, tetapi hanya berlangsung untuk perintah sederhana saat ini. echoakan melihat nilai 10untuk A, jika itu diperhatikan.
Gilles 'SANGAT berhenti menjadi jahat'
@Gilles: terima kasih banyak untuk klarifikasi! Saya tidak menyadari kehalusan ini. Jadi, jika saya mengerti dengan benar, bash harus ditetapkan untuk lingkungan saat ini jika tidak builtin (yang tidak menelurkan yang baru pid) tidak akan melihat tugas seperti perintah "regurlar" lainnya. Tapi itu terhapus setelah perintah dilakukan untuk membatasi ruang lingkup penugasan. Apakah ini benar, saya akan memperbaiki jawaban saya. PS: Terlepas dari segi teknis, saya masih berpikir jawaban harus menekankan pada aspek ruang lingkup, bukan urutan evaluasi, kalau tidak orang akan berpikir A=10 test; echo $Aakan mencetak 10
MestreLion
3

Salah satu cara yang mungkin bersih untuk melakukan apa yang Anda inginkan adalah dengan mengeluarkan perintah:

A=10 eval 'echo $A'

Yang pada dasarnya akan menunda penggantian nilai 10 ke dalam tempat $ A ke konteks selanjutnya (yaitu, 'di dalam' eval, yang sudah tahu tentang penugasan). Perhatikan bahwa kutipan tunggal itu penting. Konstruk semacam itu dengan bersih mengkomunikasikan penugasan ke perintah yang Anda inginkan (gema dalam kasus ini) tanpa menjalankan risiko mencemari lingkungan Anda saat ini.

nhokka
sumber