Mengapa mengatur variabel sebelum perintah legal di bash?

68

Saya baru saja menemukan beberapa jawaban seperti mem-parsing file teks yang dibatasi ... yang menggunakan konstruk:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

di mana IFSvariabel diatur sebelum readperintah.

Saya telah membaca referensi bash tetapi tidak tahu mengapa ini legal.

Saya mencoba

$ x="once upon" y="a time" echo $x $y

dari prompt perintah bash tetapi tidak mendapatkan apa-apa. Dapatkah seseorang mengarahkan saya ke tempat sintaks didefinisikan dalam referensi yang memungkinkan variabel IFS diatur dengan cara itu? Apakah ini kasus khusus atau dapatkah saya melakukan sesuatu yang serupa dengan variabel lain?

Mike Lippert
sumber

Jawaban:

69

Itu di bawah Shell Grammar, Simple Commands (penekanan ditambahkan):

Perintah sederhana adalah urutan penugasan variabel opsional diikuti dengan kata - kata dan pengalihan yang dipisahkan dengan kosong , dan diakhiri oleh operator kontrol. Kata pertama menentukan perintah yang akan dieksekusi, dan dilewatkan sebagai argumen nol. Kata-kata yang tersisa dilewatkan sebagai argumen untuk perintah yang dipanggil.

Jadi, Anda dapat melewati variabel apa pun yang Anda inginkan. Anda echomisalnya tidak bekerja karena variabel dilewatkan ke perintah, tidak diatur dalam shell. Shell mengembang $xdan $y sebelum menjalankan perintah. Ini berfungsi, misalnya:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time
derobert
sumber
Terima kasih! Saya melakukan banyak googling sebelum bertanya, dan mencoba mencari tahu di mana dikatakan bahwa dalam referensi, tanpa keberuntungan. Saya mencoba menjadi lebih baik dalam menulis skrip bash dan jawaban Anda membantu.
Mike Lippert
1
Hmm saya pikir saya perlu menemukan referensi yang lebih baik, milik saya (lihat tautan di atas) tidak mengatakan bahwa ia tidak memiliki bagian Tata Bahasa Shell.
Mike Lippert
1
@MikeLippert Lihat 3.7.4 dalam referensi itu ("Lingkungan untuk setiap perintah atau fungsi sederhana dapat ditambah sementara dengan mengawalinya dengan penetapan parameter"). Saya pikir referensi itu dari versi bash yang lebih lama. Saya baru saja berlari man bashdi sistem saya ...
derobert
29

Variabel yang didefinisikan menjadi seperti variabel lingkungan pada proses bercabang.

Jika Anda berlari

A="b" echo $A

kemudian bash pertama-tama mengembang $Ake ""dan kemudian berjalan

A="b" echo

Inilah cara yang benar:

x="once upon" y="a time" bash -c 'echo $x $y'

Perhatikan tanda kutip tunggal di bash -c, jika tidak, Anda memiliki masalah yang sama seperti di atas.

Jadi contoh loop Anda legal karena perintah bash builtin 'read' akan mencari IFS dalam variabel lingkungannya, dan menemukannya ,. Karena itu,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

akan dicetak TEST is and I is test

Terakhir, seperti untuk sintaks, dalam for loop, sebuah string diharapkan. Karena itu saya harus menggunakan backticks untuk membuatnya menjadi sebuah perintah. Namun, sementara loop mengharapkan sintaks perintah, seperti IFS=, read xx yy zz.

Mike Fairhurst
sumber
1
Terima kasih, saya belajar sedikit lebih banyak daripada yang saya minta dari jawaban Anda. Saya akan memilih jawaban Anda juga, tetapi saya belum diizinkan dan saya menandai jawaban yang diterima pada jawaban pertama.
Mike Lippert
1
Terima kasih, saya itulah yang saya harapkan. Dan memilih lebih tidak berarti daripada mendengar penghargaan Anda!
Mike Fairhurst
Untuk mengklarifikasi komentar Anda di bawah baris kode pertama: Ya, dengan Abash variabel yang tidak disetel diperluas $Ake string kosong tetapi untuk menghindari kebingungan saya tidak akan menggunakan ""karena kode tersebut tidak setara dengan A="b" echo "". Tidak akan ada argumen untuk echo.
pabouk
8

man bash

LINGKUNGAN HIDUP

[...] Lingkungan untuk perintah atau fungsi sederhana dapat ditambah sementara dengan mengawali dengan penugasan parameter, seperti dijelaskan di PARAMETER. Pernyataan penugasan ini hanya memengaruhi lingkungan yang dilihat oleh perintah itu.

Variabel diperluas sebelum penugasan variabel berlangsung. Untuk alasan yang jelas itu var=xakan bekerja sebaliknya, tetapi var=$othervartidak mau. Yaitu, Anda $xdiperlukan sebelum tersedia. Tapi itu bukan masalah utama. Masalah utama adalah bahwa baris perintah dapat dimodifikasi oleh lingkungan shell saja tetapi penugasan tidak menjadi bagian dari lingkungan shell.

Anda mencampuradukkan fitur: Anda menginginkan penggantian baris perintah tetapi memasukkan definisi variabel ke dalam lingkungan perintah. Penggantian baris perintah harus dilakukan oleh shell. Lingkungan harus secara eksplisit digunakan oleh perintah yang dipanggil. Apakah dan bagaimana ini dilakukan tergantung pada perintah.

Keuntungan dari penggunaan ini adalah Anda dapat mengatur lingkungan untuk subproses tanpa mempengaruhi lingkungan shell.

x="once upon" y="a time" bash -c 'echo $x $y'

berfungsi seperti yang Anda harapkan karena dalam hal ini kedua fitur digabungkan: Penggantian baris perintah tidak dilakukan oleh shell panggilan tetapi oleh shell subprocess.

Hauke ​​Laging
sumber
1
Ini sedikit lebih halus dari itu, karena contoh ini juga berfungsi x="once upon" y="a time" eval 'echo $x $y'ketika tidak ada subproses karena evalmerupakan builtin. Saya kira kutipan yang relevan dari manual ini adalah The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments. Mempertimbangkan contoh pertanyaan itu harus seperti ini karena readjuga builtin dan bekerja dengan keadaan sementara yang berubah IFS.
David Ongaro
4

Perintah yang Anda berikan adalah berbeda karena $xdan $ydiperluas sebelum itu echoperintah berjalan, sehingga nilai-nilai mereka di saat shell yang digunakan, bukan nilai-nilai yang echoakan Anda lihat di lingkungannya jika ingin terlihat.

chepner
sumber
Bagaimana bisa sesuatu di baris perintah diperluas setelah perintah dijalankan ...?
Hauke ​​Laging
Penugasan pra-perintah untuk xdan yuntuk lingkungan yang echoberjalan, bukan lingkungan di mana argumen untuk echodiperluas. Sebab IFS=, read xx yy zz, seluruh string dibaca, tidak terbuka, dengan readperintah. Kemudian , string yang dibagi sesuai dengan nilai IFS, dengan potongan yang sesuai ditugaskan untuk xx, yy, dan zz.
chepner
Saya hanya ingin menunjukkan bahwa kata-kata "diperluas sebelum perintah dijalankan" tidak masuk akal karena setelah dimulainya perintah tidak ada yang pernah diperluas lagi. Lebih jauh: Apakah Anda belum melihat jawaban saya atau apakah Anda percaya bahwa saya perlu penjelasan tentang apa yang terjadi ...?
Hauke ​​Laging
1
Saya tidak mengklaim Anda membutuhkan penjelasan atau bahwa apa pun dapat diperluas setelah perintah dijalankan. Namun, memang benar bahwa bashpertama mem-parsing baris perintah yang diberikan, mengakui bahwa ada dua tugas variabel untuk diterapkan pada lingkungan perintah yang akan datang, mengidentifikasi perintah untuk menjalankan ( echo), memperluas parameter yang ditemukan dalam argumen, kemudian menjalankan perintah echodengan argumen yang diperluas.
chepner
Dalam kasus echosaya tidak yakin apakah itu bisa "melihat" variabel, karena itu adalah perintah builtin dan karena itu tidak berjalan dalam subkulit yang bisa memiliki lingkungan sendiri. Tapi saya mencobanya dengan evalyang juga merupakan builtin dan memang tahu tentang itu. Misalnya coba a=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $ayang menunjukkan bahwa ahanya diatur di dalam evalmeskipun pid adalah sama dan lingkungan tidak berubah selama eval! (Untuk mengakses /procAnda perlu menjalankan ini di Linux.) Sepertinya bash melakukan beberapa sihir tambahan di sini.
David Ongaro
2

Saya akan melihat gambaran yang lebih besar tentang " mengapa ini legal"

Jawab: Agar Anda dapat memanggil atau menjalankan program dan untuk pemanggilan itu hanya menggunakan variabel dengan variabel tertentu.

Sebagai contoh: Anda memiliki param untuk koneksi basis data yang disebut 'db_connection', dan biasanya Anda meneruskan 'test' sebagai nama untuk koneksi basis data pengujian Anda. Bahkan Anda bahkan dapat menetapkannya sebagai default yang tidak perlu Anda lewati secara eksplisit. Namun terkadang Anda ingin bekerja dengan database ci. Jadi Anda lulus dalam param sebagai 'ci' dan kemudian program yang disebut menggunakan yang param database sebagai nama db digunakan untuk semua panggilan database. Untuk menjalankan selanjutnya, jika Anda tidak mengulangi pendekatan dan hanya memanggil program variabel akan kembali ke nilai default sebelumnya.

Michael Durrant
sumber
0

Anda juga bisa menggunakan ;. Ini akan dievaluasi sebelumnya karena itu pemisah perintah.

x="once upon" y="a time"; echo $x $y
camabeh
sumber
1
Ini menciptakan dua variabel shell dan tidak membuat variabel lingkungan di utilitas yang diberikan. Ini adalah hal yang sangat berbeda. Ada juga efek samping bahwa shell saat ini sekarang memiliki variabel baru di dalamnya.
Kusalananda