#! / bin / sh vs #! / bin / bash untuk portabilitas maksimum


36

Saya biasanya bekerja dengan server Ubuntu LTS yang dari apa yang saya mengerti symlink /bin/shuntuk /bin/dash. Banyak distro lain meskipun symlink /bin/shuntuk /bin/bash.

Dari itu saya mengerti bahwa jika sebuah skrip menggunakan #!/bin/shdi 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?


Ada sedikit perbedaan antara berbagai cangkang. Jika portabilitas adalah yang paling penting bagi Anda maka gunakan #!/bin/shdan jangan gunakan apa pun selain yang disediakan oleh shell asli.
Thorbjørn Ravn Andersen

Jawaban:


65

Ada sekitar empat level portabilitas untuk skrip shell (sejauh menyangkut garis shebang):

  1. Paling portabel: gunakan #!/bin/shshebang 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.)

  2. 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).

  3. 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).

  4. Least portable: gunakan #!/bin/shshebang 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 (misalnya echo -natau echo -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, gunakan printfsaja (dan pelajari cara kerjanya - ini lebih rumit daripada echobiasanya). The psperintah 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.


12
Pilihan keempat hanyalah ide yang salah. Tolong jangan gunakan itu.
pabouk

3
@ poabuk saya setuju sepenuhnya, jadi saya mengedit jawaban saya untuk membuat ini lebih jelas.
Gordon Davisson

1
Pernyataan pertama Anda sedikit menyesatkan. Standar POSIX tidak menentukan apa pun tentang shebang luar yang mengatakan menggunakannya mengarah ke perilaku yang tidak ditentukan. Selain itu, POSIX tidak menentukan di mana shell posix harus berada, hanya namanya ( sh), jadi /bin/shtidak dijamin menjadi jalan yang benar. Maka yang paling portabel adalah untuk tidak menentukan shebang sama sekali, atau untuk mengadaptasi shebang dengan OS yang digunakan.
jlliagre

2
Saya jatuh ke # 4 dengan naskah saya baru-baru ini, dan tidak tahu mengapa itu tidak berhasil; setelah semua, perintah yang sama bekerja dengan baik, dan melakukan persis apa yang saya ingin mereka lakukan, ketika saya mencobanya langsung di shell. Begitu aku berubah #!/bin/shke #!/bin/bashmeskipun, 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.)
sebuah CVn

2
@ MichaelKjörling Shell Bourne (benar) hampir tidak pernah dibundel dengan distribusi Linux dan tidak sesuai dengan POSIX. Shell standar POSIX dibuat dari kshperilaku, bukan Bourne. Apa yang dilakukan kebanyakan distribusi Linux setelah FHS adalah memiliki /bin/shtautan simbolis ke shell yang mereka pilih untuk memberikan kompatibilitas POSIX, biasanya bashatau dash.
jlliagre

5

Dalam ./configureskrip yang menyiapkan bahasa TXR untuk membangun, saya menulis prolog berikut untuk portabilitas yang lebih baik. Script akan melakukan bootstrap sendiri meskipun #!/bin/shBourne Shell lama yang tidak sesuai dengan POSIX. (Saya membuat setiap rilis di Solaris 10 VM).

#!/bin/sh

# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells

if test x$txr_shell = x ; then
  for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
    if test -x $shell ; then
       txr_shell=$shell
       break
    fi
  done
  if test x$txr_shell = x ; then
    echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
    txr_shell=/bin/sh
  fi
  export txr_shell
  exec $txr_shell $0 ${@+"$@"}
fi

# rest of the script here, executing in upgraded shell

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_shellvariabel lingkungan diatur, sehingga script ulang dieksekusi tahu itu adalah contoh rekursif ulang dieksekusi.

(Dalam skrip saya, txr_shellvariabel juga selanjutnya digunakan, untuk dua tujuan tepat: pertama itu dicetak sebagai bagian dari pesan informatif dalam output skrip. Kedua, itu diinstal sebagai SHELLvariabel dalam Makefile, sehingga makeakan menggunakan ini shell juga untuk menjalankan resep.)

Pada sistem di mana /bin/shtanda hubung, Anda dapat melihat bahwa logika di atas akan menemukan /bin/bashdan menjalankan kembali skrip dengan itu.

Pada kotak Solaris 10, /usr/xpg4/bin/shakan menendang jika tidak ada Bash ditemukan.

Prolog ditulis dalam dialek shell konservatif, menggunakan testuntuk 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).


Seseorang tidak akan memerlukan xperetasan jika kutipan yang tepat digunakan, karena situasi yang mengharuskan idiom tersebut mengelilingi permintaan tes yang sudah tidak berlaku lagi dengan -aatau -omenggabungkan beberapa tes.
Charles Duffy

@CharlesDuffy Memang; yang test x$whateveryang 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.
Kaz

2

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/shdijamin 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/shdan 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/perlada 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?..)


Suatu hari saya harus menulis naskah yang melakukan sesuatu seperti 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.
michau

@ Michau Mungkin tidak memenuhi syarat sebagai satu-liner lagi, tergantung pada apa yang terjadi di elips itu, tapi saya pikir Anda tidak mengambil "menulis ulang semuanya dalam bahasa scripting yang lebih baik" secara harfiah seperti yang saya maksudkan. Cara saya terlihat seperti perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdPerhatikan 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 perlu tar --null -T.)
zwol

Masalahnya adalah, findmengembalikan 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.
michau

BTW, aku bisa menggunakan Perl bukan Bash dengan cara cepat dan ringkas: find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. Tetapi pertanyaannya adalah: 1) Saya menggunakan find -print0dan tar --nulllagi pula, jadi apa gunanya menghindari bashism read -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.
michau
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.