Saya biasanya bekerja dengan server Ubuntu LTS yang dari apa yang saya mengerti symlink /bin/sh
untuk /bin/dash
. Banyak distro lain meskipun symlink /bin/sh
untuk /bin/bash
.
Dari itu saya mengerti bahwa jika sebuah skrip menggunakan #!/bin/sh
di atasnya mungkin tidak berjalan dengan cara yang sama di semua server?
Apakah ada praktik yang disarankan di mana shell untuk digunakan untuk skrip ketika seseorang menginginkan portabilitas maksimum dari skrip tersebut di antara server?
#!/bin/sh
dan jangan gunakan apa pun selain yang disediakan oleh shell asli.Jawaban:
Ada sekitar empat level portabilitas untuk skrip shell (sejauh menyangkut garis shebang):
Paling portabel: gunakan
#!/bin/sh
shebang dan hanya menggunakan sintaks shell dasar yang ditentukan dalam standar POSIX . Ini seharusnya bekerja pada hampir semua sistem POSIX / unix / linux. (Ya, kecuali Solaris 10 dan sebelumnya yang memiliki shell Bourne warisan asli, mendahului POSIX sehingga tidak sesuai, sebagai/bin/sh
.)Yang paling portabel kedua: gunakan garis shebang
#!/bin/bash
(atau#!/usr/bin/env bash
), dan tetap menggunakan fitur bash v3. Ini akan bekerja pada sistem apa pun yang memiliki bash (di lokasi yang diharapkan).Ketiga yang paling portabel: gunakan garis shebang
#!/bin/bash
(atau#!/usr/bin/env bash
), dan gunakan fitur bash v4. Ini akan gagal pada sistem apa pun yang memiliki bash v3 (mis. MacOS, yang harus menggunakannya untuk alasan lisensi).Least portable: gunakan
#!/bin/sh
shebang dan gunakan ekstensi bash ke sintaks POSIX shell. Ini akan gagal pada sistem apa pun yang memiliki sesuatu selain bash untuk / bin / sh (seperti versi Ubuntu terbaru). Jangan pernah melakukan ini; itu bukan hanya masalah kompatibilitas, itu hanya salah. Sayangnya, ini adalah kesalahan yang dilakukan banyak orang.Rekomendasi saya: gunakan yang paling konservatif dari tiga yang pertama yang memasok semua fitur shell yang Anda butuhkan untuk skrip. Untuk portabilitas maksimal, gunakan opsi # 1, tetapi dalam pengalaman saya beberapa fitur bash (seperti array) cukup membantu sehingga saya akan menggunakan # 2.
Hal terburuk yang dapat Anda lakukan adalah # 4, menggunakan shebang yang salah. Jika Anda tidak yakin fitur apa yang POSIX dasar dan yang merupakan ekstensi bash, baik tetap dengan bash shebang (yaitu opsi # 2), atau uji skrip secara menyeluruh dengan shell yang sangat dasar (seperti tanda hubung pada server LTS Ubuntu Anda). Wiki Ubuntu memiliki daftar bashisme yang baik untuk diperhatikan .
Ada beberapa info yang sangat bagus tentang sejarah dan perbedaan antara shell di pertanyaan Unix & Linux "Apa artinya menjadi kompatibel?" dan pertanyaan Stackoverflow "Perbedaan antara sh dan bash" .
Perlu diketahui juga bahwa shell bukan satu-satunya hal yang berbeda antara sistem yang berbeda; jika Anda terbiasa dengan linux, Anda terbiasa dengan perintah GNU, yang memiliki banyak ekstensi tidak standar yang mungkin tidak Anda temukan pada sistem unix lainnya (mis. bsd, macOS). Sayangnya, tidak ada aturan sederhana di sini, Anda hanya perlu mengetahui kisaran variasi untuk perintah yang Anda gunakan.
Salah satu perintah menjijikkan dalam hal portabilitas adalah salah satu yang paling dasar:
echo
. Setiap kali Anda menggunakannya dengan opsi apa pun (misalnyaecho -n
atauecho -e
), atau dengan lolos (garis miring terbalik) dalam string untuk mencetak, versi yang berbeda akan melakukan hal yang berbeda. Setiap kali Anda ingin mencetak string tanpa umpan baris setelahnya, atau dengan melarikan diri dalam string, gunakanprintf
saja (dan pelajari cara kerjanya - ini lebih rumit daripadaecho
biasanya). Theps
perintah juga berantakan .Hal umum lain yang harus diperhatikan adalah ekstensi terbaru / GNUish ke sintaks opsi perintah: format perintah lama (standar) adalah bahwa perintah tersebut diikuti oleh opsi (dengan tanda hubung tunggal, dan setiap opsi adalah huruf tunggal), diikuti oleh argumen perintah. Varian terbaru (dan seringkali non-portabel) mencakup opsi panjang (biasanya diperkenalkan dengan
--
), memungkinkan opsi muncul setelah argumen, dan menggunakan--
untuk memisahkan opsi dari argumen.sumber
sh
), jadi/bin/sh
tidak dijamin menjadi jalan yang benar. Maka yang paling portabel adalah untuk tidak menentukan shebang sama sekali, atau untuk mengadaptasi shebang dengan OS yang digunakan.#!/bin/sh
ke#!/bin/bash
meskipun, script bekerja dengan sempurna. (Untuk membela saya, skrip itu telah berevolusi dari waktu ke waktu dari yang benar-benar hanya membutuhkan sh-isme, ke yang bergantung pada perilaku mirip-bash.)ksh
perilaku, bukan Bourne. Apa yang dilakukan kebanyakan distribusi Linux setelah FHS adalah memiliki/bin/sh
tautan simbolis ke shell yang mereka pilih untuk memberikan kompatibilitas POSIX, biasanyabash
ataudash
.Dalam
./configure
skrip yang menyiapkan bahasa TXR untuk membangun, saya menulis prolog berikut untuk portabilitas yang lebih baik. Script akan melakukan bootstrap sendiri meskipun#!/bin/sh
Bourne Shell lama yang tidak sesuai dengan POSIX. (Saya membuat setiap rilis di Solaris 10 VM).Idenya di sini adalah bahwa kami menemukan shell yang lebih baik daripada yang kami jalankan di bawah, dan jalankan kembali skrip menggunakan shell itu. The
txr_shell
variabel lingkungan diatur, sehingga script ulang dieksekusi tahu itu adalah contoh rekursif ulang dieksekusi.(Dalam skrip saya,
txr_shell
variabel juga selanjutnya digunakan, untuk dua tujuan tepat: pertama itu dicetak sebagai bagian dari pesan informatif dalam output skrip. Kedua, itu diinstal sebagaiSHELL
variabel dalamMakefile
, sehinggamake
akan menggunakan ini shell juga untuk menjalankan resep.)Pada sistem di mana
/bin/sh
tanda hubung, Anda dapat melihat bahwa logika di atas akan menemukan/bin/bash
dan menjalankan kembali skrip dengan itu.Pada kotak Solaris 10,
/usr/xpg4/bin/sh
akan menendang jika tidak ada Bash ditemukan.Prolog ditulis dalam dialek shell konservatif, menggunakan
test
untuk tes keberadaan file, dan${@+"$@"}
trik untuk memperluas argumen yang melayani beberapa shell lama yang rusak (yang hanya akan terjadi"$@"
jika kita berada dalam shell sesuai POSIX).sumber
x
peretasan jika kutipan yang tepat digunakan, karena situasi yang mengharuskan idiom tersebut mengelilingi permintaan tes yang sudah tidak berlaku lagi dengan-a
atau-o
menggabungkan beberapa tes.test x$whatever
yang saya perpetrating ada terlihat seperti bawang di pernis. Jika kita tidak bisa mempercayai cangkang tua yang rusak untuk mengutip, maka${@+"$@"}
upaya terakhir tidak ada gunanya.Semua variasi bahasa shell Bourne secara objektif mengerikan dibandingkan dengan bahasa scripting modern seperti Perl, Python, Ruby, node.js, dan bahkan (bisa dibilang) Tcl. Jika Anda harus melakukan sesuatu yang sedikit rumit, Anda akan lebih bahagia dalam jangka panjang jika Anda menggunakan salah satu di atas daripada skrip shell.
Satu-satunya keuntungan bahasa shell masih memiliki lebih dari bahasa-bahasa yang lebih baru adalah bahwa sesuatu yang memanggil itu sendiri
/bin/sh
dijamin ada pada apa pun yang dimaksudkan sebagai Unix. Namun, sesuatu yang bahkan mungkin tidak sesuai dengan POSIX; banyak dari warisan Unix miliknya membekukan bahasa yang diterapkan oleh/bin/sh
dan utilitas di PATH default sebelum perubahan yang diminta oleh Unix95 (ya, Unix95, dua puluh tahun yang lalu dan terus bertambah). Mungkin ada satu set Unix95, atau bahkan POSIX.1-2001 jika Anda beruntung, alat-alat dalam direktori tidak pada PATH default (misalnya/usr/xpg4/bin
) tetapi mereka tidak dijamin ada.Namun, dasar-dasar Perl lebih mungkin untuk hadir pada instalasi Unix yang dipilih secara sewenang-wenang daripada Bash. (Dengan "dasar-dasar Perl" yang saya maksud
/usr/bin/perl
ada dan beberapa , mungkin versi yang cukup lama, dari Perl 5, dan jika Anda beruntung, set modul yang dikirimkan dengan versi interpreter juga tersedia.)Karena itu:
Jika Anda menulis sesuatu yang harus berfungsi di mana saja yang dimaksudkan sebagai Unix (seperti skrip "configure"), Anda perlu menggunakan
#! /bin/sh
, dan Anda tidak perlu menggunakan ekstensi apa pun. Saat ini saya akan menulis shell yang sesuai dengan POSIX.1-2001 dalam situasi ini, tetapi saya akan siap untuk memperbaiki POSIXisms jika seseorang meminta dukungan untuk besi yang berkarat.Tetapi jika Anda tidak menulis sesuatu yang harus bekerja di mana-mana, maka saat Anda tergoda untuk menggunakan Bashism sama sekali, Anda harus berhenti dan menulis ulang semuanya dalam bahasa scripting yang lebih baik. Masa depan Anda akan berterima kasih.
(Jadi ketika sedang pantaskah untuk menggunakan Bash ekstensi Untuk urutan pertama: tidak pernah Untuk urutan kedua: hanya untuk memperpanjang lingkungan yang interaktif Bash - misalnya untuk menyediakan pintar tab-completion dan mewah petunjuknya?..)
sumber
find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -
. Tidak mungkin membuatnya berfungsi dengan benar tanpa bashism (read -d ""
) dan ekstensi GNU (find -print0
,tar --null
). Menerapkan kembali baris itu di Perl akan lebih lama dan lebih jelas. Ini adalah situasi ketika menggunakan bashism dan ekstensi non-POSIX lainnya adalah Hal yang Benar untuk dilakukan.perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmd
Perhatikan bahwa Anda tidak hanya perlu bashism, Anda tidak memerlukan ekstensi tar GNU. (Jika panjang baris perintah menjadi perhatian atau Anda ingin pemindaian dan tar berjalan secara paralel, maka Anda memang perlutar --null -T
.)find
mengembalikan lebih dari 50.000 nama file dalam skenario saya, sehingga skrip Anda cenderung mencapai batas panjang argumen. Implementasi yang benar yang tidak menggunakan ekstensi non-POSIX harus membangun output tar secara bertahap, atau mungkin menggunakan Archive :: Tar :: Stream dalam kode Perl. Tentu saja, ini bisa dilakukan, tetapi dalam hal ini skrip Bash jauh lebih cepat untuk ditulis dan memiliki pelat yang lebih sedikit, dan karenanya lebih sedikit ruang untuk bug.find ... -print0 |perl -0ne '... print ...' |tar c --null -T -
. Tetapi pertanyaannya adalah: 1) Saya menggunakanfind -print0
dantar --null
lagi pula, jadi apa gunanya menghindari bashismread -d ""
, yang merupakan hal yang persis sama (ekstensi GNU untuk menangani pemisah nol yang, tidak seperti Perl, dapat menjadi hal POSIX suatu hari nanti) )? 2) Apakah Perl benar-benar lebih luas daripada Bash? Saya telah menggunakan gambar Docker baru-baru ini, dan banyak dari mereka memiliki Bash tetapi tidak ada Perl. Skrip baru lebih cenderung dijalankan di sana, daripada di Unices kuno.