sepertinya Bash adalah bahasa lengkap Turing
Konsep kelengkapan Turing sepenuhnya terpisah dari banyak konsep lain yang berguna dalam bahasa untuk pemrograman dalam skala besar : kegunaan, ekspresif, pemahaman, kecepatan, dll.
Jika Turing-kelengkapan adalah semua yang kami butuhkan, kami tidak akan memiliki bahasa pemrograman sama sekali , bahkan bahasa assembly . Pemrogram komputer semua hanya akan menulis dalam kode mesin , karena CPU kami juga Turing-lengkap.
mengapa Bash digunakan hampir secara eksklusif untuk menulis skrip yang relatif sederhana?
Skrip shell besar dan kompleks - seperti configure
skrip yang dihasilkan oleh GNU Autoconf - tidak lazim karena berbagai alasan:
Hingga relatif baru, Anda tidak dapat mengandalkan memiliki shell yang kompatibel dengan POSIX di mana-mana .
Banyak sistem, terutama yang lebih tua, secara teknis memiliki cangkang kompatibel POSIX di suatu tempat pada sistem, tetapi mungkin tidak berada di lokasi yang dapat diprediksi seperti /bin/sh
. Jika Anda menulis skrip shell dan harus dijalankan pada banyak sistem yang berbeda, lalu bagaimana Anda menulis baris shebang ? Salah satu opsi adalah melanjutkan dan menggunakan /bin/sh
, tetapi memilih untuk membatasi diri Anda sendiri ke dialek kulit Bourne pra-POSIX jika itu dijalankan pada sistem seperti itu.
Pra-POSIX Shell Bourne bahkan tidak memiliki aritmatika internal; Anda harus memanggil expr
atau bc
menyelesaikannya.
Bahkan dengan shell POSIX, Anda kehilangan array asosiatif dan fitur lain yang kami harapkan akan ditemukan dalam bahasa scripting Unix sejak Perl pertama kali menjadi populer di awal 1990-an .
Fakta sejarah itu berarti ada tradisi selama puluhan tahun mengabaikan banyak fitur kuat dalam penerjemah skrip shell keluarga Bourne murni karena Anda tidak dapat mengandalkannya di mana-mana.
Ini masih berlanjut hingga hari ini, pada kenyataannya: Bash tidak mendapatkan array asosiatif sampai versi 4 , tetapi Anda mungkin akan terkejut betapa banyak sistem yang masih digunakan didasarkan pada Bash 3. Apple masih mengirimkan Bash 3 dengan macOS pada 2017 - tampaknya untuk alasan lisensi - dan server Unix / Linux sering menjalankan semua tetapi tidak tersentuh dalam produksi untuk waktu yang sangat lama, sehingga Anda mungkin memiliki sistem lama yang stabil masih menjalankan Bash 3, seperti kotak CentOS 5. Jika Anda memiliki sistem seperti itu di lingkungan Anda, Anda tidak dapat menggunakan array asosiatif dalam skrip shell yang harus dijalankan pada mereka.
Jika jawaban Anda untuk masalah itu adalah bahwa Anda hanya menulis skrip shell untuk sistem "modern", Anda kemudian harus mengatasi kenyataan bahwa titik referensi umum terakhir untuk sebagian besar shell Unix adalah standar shell POSIX , yang sebagian besar tidak berubah karena itu diperkenalkan pada tahun 1989. Ada banyak cangkang berbeda berdasarkan standar itu, tetapi mereka semua memiliki tingkat yang berbeda dari standar itu. Untuk mengambil array asosiatif lagi, bash
, zsh
, dan ksh93
semua memiliki fitur itu, tapi ada beberapa yang tidak kompatibel implementasi. Pilihan Anda, maka, adalah hanya menggunakan Bash, atau hanya menggunakan Zsh, atau hanya menggunakan ksh93
.
Jika jawaban Anda untuk masalah itu adalah, "jadi instal saja Bash 4," atau ksh93
, atau apa pun, lalu mengapa tidak "hanya" menginstal Perl atau Python atau Ruby saja? Itu tidak dapat diterima dalam banyak kasus; masalah bawaan.
Tidak ada modul bahasa scripting shell Bourne keluarga yang mendukung .
Yang paling dekat dengan sistem modul dalam skrip shell adalah .
perintah - alias source
dalam varian cangkang Bourne yang lebih modern - yang gagal pada beberapa level relatif terhadap sistem modul yang tepat, yang paling mendasar di antaranya adalah namespacing .
Terlepas dari bahasa pemrograman, pemahaman manusia mulai ditandai ketika setiap file dalam program keseluruhan yang lebih besar melebihi beberapa ribu baris. Alasan utama kami menyusun program besar menjadi banyak file adalah agar kami dapat mengabstraksikan kontennya menjadi satu atau dua kalimat paling banyak. File A adalah pengurai baris perintah, file B adalah jaringan I / O pump, file C adalah shim antara library Z dan program lainnya, dll. Ketika satu-satunya metode Anda untuk merakit banyak file ke dalam satu program adalah inklusi teks , Anda membatasi seberapa besar program Anda dapat tumbuh secara wajar.
Sebagai perbandingan, akan seperti jika bahasa pemrograman C tidak memiliki linker, hanya #include
pernyataan. Dialek C-lite seperti itu tidak membutuhkan kata kunci seperti extern
atau static
. Fitur-fitur itu ada untuk memungkinkan modularitas.
POSIX tidak mendefinisikan cara untuk lingkup variabel ke fungsi skrip shell tunggal, apalagi ke file.
Ini secara efektif membuat semua variabel global , yang lagi-lagi merusak modularitas dan kompabilitas.
Ada solusi untuk ini dalam shell post-POSIX - tentu saja bash
, ksh93
dan zsh
setidaknya - tetapi itu hanya membawa Anda kembali ke poin 1 di atas.
Anda dapat melihat efek dari ini dalam panduan gaya pada penulisan makro GNU Autoconf, di mana mereka merekomendasikan Anda mengawali nama variabel dengan nama makro itu sendiri, yang mengarah ke nama variabel yang sangat panjang murni untuk mengurangi kemungkinan tabrakan untuk diterima dekat nol.
Bahkan C lebih baik dalam skor ini, satu mil. Tidak hanya sebagian besar program C ditulis terutama dengan variabel fungsi-lokal, C juga mendukung pelingkupan blok, yang memungkinkan beberapa blok dalam fungsi tunggal untuk menggunakan kembali nama variabel tanpa kontaminasi silang.
Bahasa pemrograman Shell tidak memiliki perpustakaan standar.
Dimungkinkan untuk berpendapat bahwa pustaka standar bahasa scripting shell adalah isinya PATH
, tetapi itu hanya mengatakan bahwa untuk menyelesaikan segala sesuatu yang konsekuensinya, skrip shell harus memanggil seluruh program lain, mungkin yang ditulis dalam bahasa yang lebih kuat untuk mulai dengan.
Juga tidak ada arsip pustaka utilitas shell yang banyak digunakan seperti Perl CPAN . Tanpa perpustakaan besar yang tersedia kode utilitas pihak ketiga, seorang programmer harus menulis lebih banyak kode dengan tangan, jadi dia kurang produktif.
Bahkan mengabaikan fakta bahwa sebagian besar skrip shell bergantung pada program eksternal yang biasanya ditulis dalam C untuk menyelesaikan sesuatu yang berguna, ada overhead dari semua pipe()
→ fork()
→ exec()
rantai panggilan tersebut. Pola itu cukup efisien di Unix, dibandingkan dengan IPC dan proses peluncuran pada OS lain, tetapi di sini secara efektif menggantikan apa yang akan Anda lakukan dengan panggilan subrutin dalam bahasa scripting lain, yang masih jauh lebih efisien. Itu menempatkan batas yang serius pada batas atas kecepatan eksekusi skrip shell.
Skrip Shell memiliki sedikit kemampuan bawaan untuk meningkatkan kinerjanya melalui eksekusi paralel.
Shell Bourne punya &
, wait
dan pipeline untuk ini, tapi itu sebagian besar hanya berguna untuk menyusun beberapa program, bukan untuk mencapai paralelisme CPU atau I / O. Anda tidak mungkin dapat mematok inti atau menjenuhkan array RAID hanya dengan skrip shell, dan jika Anda melakukannya, Anda mungkin bisa mencapai kinerja yang jauh lebih tinggi dalam bahasa lain.
Jaringan pipa khususnya adalah cara yang lemah untuk meningkatkan kinerja melalui eksekusi paralel. Itu hanya memungkinkan dua program berjalan secara paralel, dan salah satu dari dua kemungkinan akan diblokir pada I / O ke atau dari yang lain pada suatu titik waktu tertentu.
Ada cara-cara jaman akhir di sekitar ini, seperti xargs -P
dan GNUparallel
, tetapi ini hanya beralih ke poin 4 di atas.
Dengan tidak adanya kemampuan bawaan untuk mengambil keuntungan penuh dari sistem multi-prosesor, skrip shell akan selalu lebih lambat daripada program yang ditulis dengan baik dalam bahasa yang dapat menggunakan semua prosesor dalam sistem. Untuk mengambil configure
contoh skrip Autoconf GNU itu lagi, menggandakan jumlah inti dalam sistem tidak akan banyak membantu meningkatkan kecepatannya.
Bahasa skrip shell tidak memiliki pointer atau referensi .
Ini mencegah Anda dari melakukan banyak hal dengan mudah dilakukan dalam bahasa pemrograman lain.
Untuk satu hal, ketidakmampuan untuk merujuk secara tidak langsung ke struktur data lain dalam memori program berarti Anda terbatas pada struktur data bawaan . Shell Anda mungkin memiliki array asosiatif , tetapi bagaimana penerapannya? Ada beberapa kemungkinan, masing-masing dengan pengorbanan yang berbeda: pohon merah-hitam , pohon AVL , dan tabel hash adalah yang paling umum, tetapi ada yang lain. Jika Anda memerlukan serangkaian pengorbanan yang berbeda, Anda buntu, karena tanpa referensi, Anda tidak memiliki cara untuk memutarbalikkan banyak jenis struktur data tingkat lanjut. Anda terjebak dengan apa yang diberikan kepada Anda.
Atau, mungkin Anda membutuhkan struktur data yang bahkan tidak memiliki alternatif yang memadai yang dibangun ke dalam juru bahasa skrip shell Anda, seperti grafik asiklik terarah , yang mungkin Anda perlukan untuk memodelkan grafik dependensi . Saya telah memprogram selama beberapa dekade, dan satu-satunya cara saya bisa memikirkan untuk melakukan itu dalam skrip shell adalah dengan menyalahgunakan sistem file , menggunakan symlinks sebagai referensi palsu. Itulah jenis solusi yang Anda dapatkan ketika Anda hanya mengandalkan kelengkapan Turing, yang tidak memberi tahu Anda apakah solusi itu elegan, cepat, atau mudah dimengerti.
Struktur data lanjutan hanyalah satu penggunaan untuk pointer dan referensi. Ada tumpukan aplikasi lain untuk mereka , yang tidak bisa dilakukan dengan mudah dalam bahasa skrip shell keluarga Bourne.
Saya bisa terus dan terus, tetapi saya pikir Anda mengerti maksudnya di sini. Sederhananya, ada banyak bahasa pemrograman yang lebih kuat untuk sistem tipe Unix.
Ini adalah keuntungan besar, yang dapat mengimbangi mediokritas bahasa itu sendiri dalam beberapa kasus.
Tentu, dan itulah mengapa GNU Autoconf menggunakan subset sengaja dari keluarga Bourne bahasa skrip shell untuk configure
output skripnya: sehingga configure
skripnya akan berjalan cukup banyak di mana-mana.
Anda mungkin tidak akan menemukan kelompok orang percaya yang lebih besar dalam kegunaan menulis dalam dialek kulit Bourne yang sangat portabel daripada pengembang GNU Autoconf, namun kreasi mereka sendiri ditulis terutama dalam Perl, ditambah beberapa m4
, dan hanya sedikit shell naskah; hanya keluaran Autoconf yang merupakan skrip shell Bourne murni. Jika itu tidak menimbulkan pertanyaan tentang seberapa berguna konsep "Bourne everywhere", saya tidak tahu apa yang akan terjadi.
Jadi, apakah ada batasan seberapa rumit program semacam itu bisa didapat?
Secara teknis, tidak, seperti yang disarankan pengamatan kelengkapan Turing Anda.
Tapi itu tidak sama dengan mengatakan bahwa skrip shell yang besar dan sewenang-wenang itu menyenangkan untuk ditulis, mudah di-debug, atau dieksekusi dengan cepat.
Apakah mungkin untuk menulis, katakanlah, file kompresor / decompressor di bash murni?
"Murni" Bash, tanpa ada panggilan untuk hal-hal di PATH
? Kompresor mungkin dapat dilakukan dengan menggunakan echo
dan melepaskan urutan hex, tetapi akan cukup menyakitkan untuk dilakukan. Dekompresor mungkin tidak dapat ditulis seperti itu karena ketidakmampuan untuk menangani data biner dalam shell . Anda akhirnya memanggil od
dan menerjemahkan data biner ke format teks, cara asli shell dalam menangani data.
Setelah Anda mulai berbicara tentang menggunakan shell scripting seperti yang dimaksudkan, seperti lem untuk mendorong program lain di PATH
, pintu terbuka, karena sekarang Anda hanya terbatas pada apa yang dapat dilakukan dalam bahasa pemrograman lain, yang berarti Anda tidak memiliki batasan sama sekali. Sebuah shell script yang mendapat semua kekuatannya dengan menelepon ke program lain di PATH
tidak berlari secepat program monolitik yang ditulis dalam bahasa yang lebih kuat, tetapi tidak berjalan.
Dan itu intinya. Jika Anda membutuhkan program untuk menjalankan cepat, atau jika perlu kuat sendiri daripada meminjam kekuatan dari orang lain, Anda tidak menulisnya dalam shell.
Gim video sederhana?
Berikut Tetris di shell . Game seperti lainnya tersedia, jika Anda mencari.
hanya ada alat debugging yang sangat terbatas
Saya akan menempatkan dukungan alat debugging turun sekitar 20 tempat pada daftar fitur yang diperlukan untuk mendukung pemrograman dalam ukuran besar. Banyak programmer yang lebih mengandalkan printf()
debugging daripada debugger yang tepat, terlepas dari bahasa.
Dalam shell, Anda memiliki echo
dan set -x
, yang bersama-sama cukup untuk men-debug banyak masalah besar.
sh
Scriptconfigure
yang digunakan sebagai bagian dari proses membangun untuk un banyak * x paket tidak 'relatif sederhana'.