Lebih dari bahasa lain yang saya tahu, saya telah "mempelajari" Bash dengan Googling setiap kali saya membutuhkan sesuatu. Akibatnya, saya bisa menambal bersama skrip kecil yang tampaknya berfungsi. Namun, saya tidak begitu tahu apa yang terjadi, dan saya berharap untuk pengenalan yang lebih formal ke Bash sebagai bahasa pemrograman. Misalnya: Apa urutan evaluasinya? apa aturan pelingkupannya? Apa disiplin mengetik, misalnya apakah semuanya adalah string? Apa status program - apakah itu penugasan nilai-kunci dari string ke nama variabel; apakah ada lebih dari itu, misalnya tumpukan? Apakah ada tumpukan? Dan seterusnya.
Saya berpikir untuk berkonsultasi dengan manual GNU Bash untuk pemahaman semacam ini, tetapi sepertinya bukan itu yang saya inginkan; ini lebih merupakan daftar cucian gula sintaksis daripada penjelasan dari model semantik inti. Jutaan-dan-satu "tutorial bash" online hanya lebih buruk. Mungkin saya harus belajar dulu sh
, dan memahami Bash sebagai gula sintaksis di atas ini? Saya tidak tahu apakah ini model yang akurat.
Ada saran?
EDIT: Saya telah diminta untuk memberikan contoh tentang apa yang idealnya saya cari. Contoh yang agak ekstrim dari apa yang saya anggap sebagai "semantik formal" adalah makalah tentang "esensi JavaScript" ini . Mungkin contoh yang sedikit kurang formal adalah laporan Haskell 2010 .
sumber
Jawaban:
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 # Tested as-is on OS X, without the `]` 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: # We're in a child process 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 lakukanfork
), 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: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' # another way to transiently export a name 123
Anda dapat menggabungkan aturan pelingkupan ini:
$ foo() { > local -x bar=123 # Export foo, but only in this scope > 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 ekspansiMungkin bermanfaat untuk menunjukkan bahwa kata kosong sebenarnya hanyalah sebuah kata, dan kutipan itu tidak mengubah apa pun tentang itu.
Ekspansi substring$ echo foo foo $ 'echo' foo foo $ "echo" foo foo
$ fail='echoes' $ set -x # So we can see what's going on $ "${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 # Must re-evaluate the assignment $ echo $foo 20 $ echo "${foo:0:1}" # Still just a string 2
Array
Argumen dan Parameter PosisiSebelum 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 menggunakanset
ataushift
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
Array$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.Sintaks array dimodelkan setelah parameter posisi, jadi sebaiknya anggap array sebagai jenis "parameter posisi eksternal", jika Anda suka. Array dapat dideklarasikan menggunakan pendekatan berikut:
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" # Even if the zeroth index isn't set $ …
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:
$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.shift
sebuah array, Anda harus memotong dan menetapkannya kembali, sepertiarr=( "${arr[@]:1}" )
. Anda juga bisa melakukannyaunset arr[0]
, tapi itu akan membuat elemen pertama di indeks 1.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:$ startx
)$ yes | make config
) (lol)$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)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:
SHELL GRAMMAR
bagian bash manualTolong beri komentar jika Anda ingin saya memperluas lebih jauh ke arah tertentu.
sumber
yes | make config
;-) Tapi serius, artikel yang sangat bagus./bin/[
dan/bin/test
sering kali merupakan penerapan yang sama 2) "Asumsikan kata pertama adalah sebuah perintah." - Berharap ketika Anda melakukan tugas ...execle
dan menginterpolasi kata pertama ke dalam lingkungan, tetapi itu masih membuatnya sedikit lebih rumit.a = 1
tidak bekerja).Jawaban atas pertanyaan Anda "Apa disiplin mengetik, misalnya apakah semuanya string" Variabel Bash adalah string karakter. Tapi, Bash mengizinkan operasi aritmatika dan perbandingan pada variabel ketika variabel adalah bilangan bulat. Pengecualian untuk variabel aturan Bash adalah string karakter ketika variabel tersebut diketik atau dideklarasikan sebaliknya
$ A=10/2 $ echo "A = $A" # Variable A acting like a String. A = 10/2 $ B=1 $ let B="$B+1" # Let is internal to bash. $ echo "B = $B" # One is added to B was Behaving as an integer. B = 2 $ A=1024 # A Defaults to string $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ echo "B = $B" # $B STRING is a string B = 10STRING01 $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ declare -i B $ echo "B = $B" # Declaring a variable with non-integers in it doesn't change the contents. B = 10STRING01 $ B=${B/STRING01/24} # Substitute "STRING01" with "24". $ echo "B = $B" B = 1024 $ declare -i B=10/2 # Declare B and assigning it an integer value $ echo "B = $B" # Variable B behaving as an Integer B = 5
Deklarasikan arti opsi:
sumber
Halaman manual bash memiliki lebih banyak info daripada kebanyakan halaman manual, dan mencakup beberapa dari apa yang Anda minta. Asumsi saya setelah lebih dari satu dekade scripting bash adalah, karena 'sejarahnya sebagai perpanjangan dari sh, ia memiliki beberapa sintaks yang funky (untuk menjaga kompatibilitas ke belakang dengan sh).
FWIW, pengalaman saya seperti pengalaman Anda; walaupun berbagai buku (misalnya, O'Reilly "Learning the Bash Shell" dan sejenisnya) membantu dengan sintaks, ada banyak cara aneh untuk memecahkan berbagai masalah, dan beberapa di antaranya tidak ada dalam buku dan harus dicari di Google.
sumber