Shell adalah antarmuka untuk sistem operasi. Ini biasanya merupakan bahasa pemrograman yang kurang lebih kuat dalam dirinya sendiri, tetapi dengan fitur yang dirancang untuk membuatnya mudah berinteraksi secara khusus dengan sistem operasi dan sistem file. Semantik POSIX shell (selanjutnya disebut hanya sebagai "shell") adalah sedikit mutt, menggabungkan beberapa fitur LISP (ekspresi-s memiliki banyak kesamaan dengan pemisahan kata shell ) dan C (banyak dari sintaks aritmatika shell semantik berasal dari C).
Akar lain dari sintaks shell berasal dari asuhannya sebagai campuran utilitas UNIX individu. Sebagian besar dari apa yang sering dibangun di dalam shell sebenarnya dapat diimplementasikan sebagai perintah eksternal. Itu melempar banyak orang baru shell untuk satu lingkaran ketika mereka menyadari bahwa /bin/[
ada di banyak sistem.
$ if '/bin/[' -f '/bin/['; then echo t; fi
t
wat?
Ini jauh lebih masuk akal jika Anda melihat bagaimana shell diimplementasikan. Inilah implementasi yang saya lakukan sebagai latihan. Ini dengan Python, tapi saya harap itu bukan hangup untuk siapa pun. Ini tidak terlalu kuat, tetapi instruktif:
#!/usr/bin/env python
from __future__ import print_function
import os, sys
'''Hacky barebones shell.'''
try:
input=raw_input
except NameError:
pass
def main():
while True:
cmd = input('prompt> ')
args = cmd.split()
if not args:
continue
cpid = os.fork()
if cpid == 0:
os.execl(args[0], *args)
else:
os.waitpid(cpid, 0)
if __name__ == '__main__':
main()
Saya harap penjelasan di atas menjelaskan bahwa model eksekusi shell cukup banyak:
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
Ekspansi, resolusi perintah, eksekusi. Semua semantik shell terikat dalam salah satu dari tiga hal ini, meskipun jauh lebih kaya daripada implementasi yang saya tulis di atas.
Tidak semua perintah fork
. Faktanya, ada beberapa perintah yang tidak masuk akal diimplementasikan sebagai eksternal (seperti yang harus mereka lakukan fork
), tetapi bahkan perintah tersebut sering tersedia sebagai perintah eksternal untuk kepatuhan POSIX yang ketat.
Bash membangun di atas dasar ini dengan menambahkan fitur dan kata kunci baru untuk meningkatkan shell POSIX. Ini hampir kompatibel dengan sh, dan bash ada di mana-mana sehingga beberapa penulis skrip pergi bertahun-tahun tanpa menyadari bahwa skrip mungkin tidak benar-benar berfungsi pada sistem ketat POSIXly. (Saya juga bertanya-tanya bagaimana orang bisa begitu peduli tentang semantik dan gaya satu bahasa pemrograman, dan begitu sedikit untuk semantik dan gaya shell, tapi saya menyimpang.)
Urutan evaluasi
Ini adalah sedikit pertanyaan jebakan: Bash menafsirkan ekspresi dalam sintaks utamanya dari kiri ke kanan, tetapi dalam sintaks aritmatika, ia mengikuti diutamakan C. Ekspresi berbeda dari ekspansi . Dari EXPANSION
bagian manual bash:
Urutan ekspansi adalah: ekspansi brace; ekspansi tilde, ekspansi parameter dan variabel, ekspansi aritmatika, dan substitusi perintah (dilakukan dengan cara kiri-ke-kanan); pemisahan kata; dan perluasan nama jalur.
Jika Anda memahami pemisahan kata, perluasan nama jalur, dan perluasan parameter, Anda sedang dalam perjalanan untuk memahami sebagian besar dari apa yang dilakukan bash. Perhatikan bahwa perluasan nama jalur yang muncul setelah pemisahan kata sangat penting, karena ini memastikan bahwa file dengan spasi dalam namanya masih dapat dicocokkan oleh glob. Inilah sebabnya mengapa penggunaan ekspansi glob lebih baik daripada perintah parsing secara umum.
Cakupan
Lingkup fungsi
Sama seperti ECMAscript lama, shell memiliki cakupan dinamis kecuali Anda secara eksplisit mendeklarasikan nama dalam suatu fungsi.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
Lingkungan dan proses "ruang lingkup"
Subkulit mewarisi variabel dari cangkang induknya, tetapi jenis proses lain tidak mewarisi nama yang tidak diekspor.
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y'
123
Anda dapat menggabungkan aturan pelingkupan ini:
$ foo() {
> local -x bar=123
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
Disiplin mengetik
Um, tipe. Ya. Bash benar-benar tidak memiliki tipe, dan semuanya berkembang menjadi string (atau mungkin sebuah kata akan lebih sesuai.) Tapi mari kita periksa berbagai tipe ekspansi.
String
Hampir semua hal bisa diperlakukan sebagai string. Bareword dalam bash adalah string yang artinya bergantung sepenuhnya pada ekspansi yang diterapkan padanya.
Tidak ada ekspansi
Mungkin bermanfaat untuk menunjukkan bahwa kata kosong sebenarnya hanyalah sebuah kata, dan kutipan itu tidak mengubah apa pun tentang itu.
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
Ekspansi substring
$ fail='echoes'
$ set -x
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
Untuk lebih lanjut tentang ekspansi, baca Parameter Expansion
bagian manual. Ini cukup kuat.
Bilangan bulat dan ekspresi aritmatika
Anda dapat mengilhami nama dengan atribut integer untuk memberi tahu shell untuk memperlakukan sisi kanan ekspresi tugas sebagai aritmatika. Kemudian, ketika parameter diperluas, itu akan dievaluasi sebagai matematika integer sebelum meluas ke… string.
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo
$ echo $foo
20
$ echo "${foo:0:1}"
2
Array
Argumen dan Parameter Posisi
Sebelum berbicara tentang array, mungkin ada baiknya membahas parameter posisi. Argumen untuk script shell dapat diakses menggunakan parameter bernomor, $1
, $2
, $3
, dll Anda dapat mengakses semua parameter ini sekaligus menggunakan "$@"
, yang ekspansi memiliki banyak kesamaan dengan array. Anda dapat mengatur dan mengubah parameter posisi menggunakan set
atau shift
builtins, atau cukup dengan memanggil fungsi shell atau shell dengan parameter berikut:
$ bash -c 'for ((i=1;i<=$#;i++)); do
> printf "\$%d => %s\n" "$i" "${@:i:1}"
> done' -- foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showpp() {
> local i
> for ((i=1;i<=$#;i++)); do
> printf '$%d => %s\n' "$i" "${@:i:1}"
> done
> }
$ showpp foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showshift() {
> shift 3
> showpp "$@"
> }
$ showshift foo bar baz biz quux xyzzy
$1 => biz
$2 => quux
$3 => xyzzy
Manual bash terkadang juga mengacu pada $0
parameter posisi. Saya menemukan ini membingungkan, karena tidak memasukkannya dalam hitungan argumen $#
, tetapi ini adalah parameter bernomor, jadi meh. $0
adalah nama shell atau skrip shell saat ini.
Array
Sintaks array dimodelkan setelah parameter posisi, jadi sebaiknya anggap array sebagai jenis "parameter posisi eksternal", jika Anda suka. Array dapat dideklarasikan menggunakan pendekatan berikut:
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
Anda dapat mengakses elemen array dengan indeks:
$ echo "${foo[1]}"
element1
Anda dapat mengiris array:
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
Jika Anda memperlakukan sebuah array sebagai parameter normal, Anda akan mendapatkan indeks ke nol.
$ echo "$baz"
element0
$ echo "$bar"
$ …
Jika Anda menggunakan tanda kutip atau garis miring terbalik untuk mencegah pemisahan kata, array akan mempertahankan pemisahan kata yang ditentukan:
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
Perbedaan utama antara array dan parameter posisi adalah:
- Parameter posisi tidak jarang. Jika
$12
disetel, Anda bisa yakin $11
disetel juga. (Ini bisa disetel ke string kosong, tapi $#
tidak lebih kecil dari 12.) Jika "${arr[12]}"
disetel, tidak ada jaminan yang "${arr[11]}"
disetel, dan panjang larik bisa sekecil 1.
- Elemen ke-nol dari sebuah larik jelas merupakan elemen ke-nol dari larik tersebut. Dalam parameter posisi, elemen ke nol bukanlah argumen pertama , tetapi nama skrip shell atau shell.
- Untuk
shift
sebuah array, Anda harus memotong dan menetapkannya kembali, seperti arr=( "${arr[@]:1}" )
. Anda juga bisa melakukannya unset arr[0]
, tapi itu akan membuat elemen pertama di indeks 1.
- Array dapat dibagikan secara implisit di antara fungsi shell sebagai global, tetapi Anda harus secara eksplisit meneruskan parameter posisi ke fungsi shell agar dapat melihatnya.
Seringkali nyaman untuk menggunakan perluasan nama jalur untuk membuat array nama file:
$ dirs=( */ )
Perintah
Perintah adalah kuncinya, tetapi juga dibahas secara lebih mendalam daripada yang saya bisa dengan manual. Bacalah SHELL GRAMMAR
bagiannya. Jenis perintah yang berbeda adalah:
- Perintah Sederhana (mis.
$ startx
)
- Saluran pipa (misalnya
$ yes | make config
) (lol)
- Daftar (mis.
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- Perintah Majemuk (mis.
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- Coprocesses (Kompleks, tidak ada contoh)
- Fungsi (Perintah gabungan bernama yang dapat diperlakukan sebagai perintah sederhana)
Model Eksekusi
Model eksekusi tentu saja melibatkan heap dan stack. Ini endemik untuk semua program UNIX. Bash juga memiliki stack panggilan untuk fungsi shell, terlihat melalui penggunaan caller
builtin yang bertingkat .
Referensi:
- The
SHELL GRAMMAR
bagian bash manual
- The XCU Shell Command Bahasa dokumentasi
- The Bash Panduan di wiki Greycat ini.
- Pemrograman Lanjutan di Lingkungan UNIX
Tolong beri komentar jika Anda ingin saya memperluas lebih jauh ke arah tertentu.