#! / bin / sh ile #! / bin / bash arasında maksimum taşınabilirlik


36

Genellikle I sembolik anladığım Ubuntu LTS sunucularıyla çalışmak /bin/shiçin /bin/dash. Sembolik bağ olsa diğer dağıtımlar bir sürü /bin/shiçin /bin/bash.

Bundan anlayan bir komut dosyası #!/bin/shüstünde kullanırsa , tüm sunucularda aynı şekilde çalışmayabilir?

Bu komut dosyalarının sunucular arasında maksimum taşınabilirliği istendiğinde, komut dosyaları için hangi kabuğun kullanılması konusunda önerilen bir uygulama var mı?


Çeşitli mermiler arasında küçük farklılıklar vardır. Taşınabilirlik sizin için en önemlisi ise #!/bin/sh, orijinal kabuğun sağladığından başka bir şey kullanmayın ve kullanmayın.
Thorbjørn Ravn Andersen

Yanıtlar:


65

Kabuk betikleri için kabaca dört taşınabilirlik düzeyi vardır (shebang satırına gelince):

  1. Çoğu taşınabilir: bir #!/bin/shshebang kullanın ve yalnızca POSIX standardında belirtilen temel kabuk sözdizimini kullanın . Bu hemen hemen tüm POSIX / unix / linux sistemlerinde çalışmalıdır. (Peki, Solaris 10 ve önceki sürümleri hariç, gerçek eski Bourne kabuğuna sahipti, POSIX'i olduğu gibi uymuyordu /bin/sh.)

  2. En taşınabilir ikinci: Bir #!/bin/bash(veya #!/usr/bin/env bash) shebang hattı kullanın ve v3'ün özelliklerini vurgulayın. Bu (beklenen konumda) bash olan herhangi bir sistem üzerinde çalışacak.

  3. Üçüncü en taşınabilir: bir #!/bin/bash(veya #!/usr/bin/env bash) shebang hattı kullanın ve bash v4 özelliklerini kullanın. Bu, bash v3 olan herhangi bir sistemde başarısız olacaktır (örneğin, lisans nedeniyle kullanmak zorunda olan macOS).

  4. En taşınabilir: bir #!/bin/shshebang kullanın ve POSIX kabuk sözdiziminde bash uzantılarını kullanın. Bu, / bin / sh için bash dışında bir şey olan herhangi bir sistemde başarısız olur (örneğin, en son Ubuntu sürümleri gibi). Bunu asla yapma; Bu sadece bir uyumluluk sorunu değil, sadece yanlış bir şey. Ne yazık ki, birçok insanın yaptığı bir hata.

Tavsiyem: Senaryo için ihtiyacınız olan tüm kabuk özelliklerini sağlayan ilk üçün en muhafazakarını kullanın. Maksimum taşınabilirlik için # 1 seçeneğini kullanın, ancak benim deneyimime göre bazı bash özellikleri (diziler gibi) 2 ile gireceğim kadar faydalı.

Yapabileceğiniz en kötü şey # 4, yanlış shebang kullanarak. Hangi özelliklerin temel POSIX ve bash uzantıları olduğundan emin değilseniz, bir bash shebang'a (yani, seçenek # 2) yapıştırın veya komut dosyasını çok temel bir kabukla (Ubuntu LTS sunucularınızdaki çizgi gibi) iyice test edin. Ubuntu wiki'de dikkat edilmesi gereken iyi bir temel listesi vardır .

Unix ve Linux sorusundaki " kabuklarla uyumlu olmanın anlamı ne?" ve Stackoverflow sorusu "sh ve bash arasındaki fark" .

Ayrıca, kabuğun farklı sistemler arasında farklılık gösteren tek şey olmadığını unutmayın; Linux'a alışkınsanız, diğer unix sistemlerinde (örn. bsd, macOS) bulamayacağınız birçok standart dışı uzantıya sahip olan GNU komutlarına alışkınsınız. Ne yazık ki, burada basit bir kural yoktur, yalnızca kullandığınız komutlar için varyasyon aralığını bilmeniz gerekir.

Taşınabilirlik açısından en iğrenç komutların biri en temel biridir: echo. Herhangi bir seçenekle (örneğin echo -nveya echo -e) veya dizgede yazdırmak için herhangi bir kaçış çizgisiyle (ters eğik çizgi) kullandığınız zaman, farklı sürümler farklı şeyler yapacaktır. Bir dizgiyi, bundan sonra satır besleme olmadan veya dizgiden kaçışlarla yazdırmak istediğinizde, printfonun yerine kullanın (ve nasıl çalıştığını öğrenin - olduğundan daha karmaşıktır echo). psKomuttur da bir karmaşa .

İzlenmesi gereken diğer bir genel şey ise komut seçeneği sözdizimine yeni / GNUish uzantılarıdır: eski (standart) komut formatı, komutun ardından seçeneklerin (tek bir kısa çizgi ile, ve her seçeneğin tek bir harfle) izlenmesi, ardından komut argümanları. En yeni (ve genellikle taşınabilir olmayan) değişkenler, seçeneklerin --argümanlardan sonra gelmesine izin veren ve --seçenekleri argümanlardan ayırmak için kullanılan uzun seçenekleri (genellikle birlikte verilir ) içerir .


12
Dördüncü seçenek sadece yanlış bir fikirdir. Lütfen kullanmayın.
pabouk

3
@pabouk Tamamen katılıyorum, bu yüzden bunu daha net hale getirmek için cevabımı düzenledim.
Gordon Davisson,

1
İlk ifaden biraz yanıltıcı. POSIX standardı, dışındaki shebang hakkında, belirtilmemiş davranışlara yol açtığını söyleyen herhangi bir şey belirtmez. Üstelik POSIX, posix kabuğunun nereye yerleştirileceğini, sadece adını ( sh) /bin/shbelirtmediğinden, doğru yol olacağı garanti edilmez. O zaman en taşınabilir olanı, herhangi bir shebang tanımlamamak veya shebang'ı kullanılan işletim sistemine uyarlamamaktır.
jlliagre

2
Son zamanlarda bir senaryo ile 4. sıraya düştüm ve neden işe yaramadığını anlayamadım; Sonuçta, aynı komutlar sağlam bir şekilde çalıştı ve tam olarak kabukta denediğimde, tam olarak istediklerimi yaptım. En kısa zamanda değişmiş olarak #!/bin/shkarşı #!/bin/basholsa da, senaryo mükemmel çalıştı. (Savunmam için, bu senaryo zaman içinde yalnızca sh-isms'a ihtiyaç duyan, bash benzeri davranışlara dayanan bir şeye dönüşmüştü.)
Özgeçmiş

2
@ MichaelKjörling (true) Bourne kabuğu, Linux dağıtımlarıyla neredeyse hiç bir zaman paketlenmemiştir ve yine de POSIX uyumlu değildir. POSIX standart kabuğu, kshBourne'dan değil davranıştan üretilmiştir. FHS'nin ardından Linux dağıtımlarının çoğunun yaptığı şey, /bin/shPOSIX uyumluluğu sağlamak için seçtikleri kabuğa sembolik bir bağ olması bashveya genellikle dash.
jlliagre

5

In ./configurebina TXR dilini hazırlar komut, ben daha iyi taşınabilirlik için aşağıdaki prolog yazdı. #!/bin/shPOSIX uyumlu olmayan eski bir Bourne Kabuğu olsa bile , komut dosyası kendisini önyükleyecektir . (Her sürümü bir Solaris 10 VM'de yapıyorum).

#!/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

Buradaki fikir, altında çalıştığımızdan daha iyi bir kabuk bulmamız ve bu kabuğu kullanarak betiği yeniden çalıştırmamızdır. txr_shellOrtam değişkeni yeniden idam programcığın yeniden idam özyinelemeli örneğidir bilir böylece, ayarlanır.

(Senaryom olarak, txr_shelldeğişken da sonradan tam iki amaçla kullanılır: ilk olarak yazdırılır komut çıktısında bilgilendirici bir mesajın parçası olarak İkinci olarak, olarak yüklenir. SHELLDeğişken Makefile, böylece makebu kullanacağız yemek tarifleri çalıştırmak için de kabuk.)

/bin/shÇizgi bulunan bir sistemde , yukarıdaki mantığın /bin/bashbetiği bununla bulup yeniden çalıştıracağını görebilirsiniz.

Bir Solaris 10 kutusunda, /usr/xpg4/bin/sheğer Bash bulunamazsa devreye girecek.

Prolog, varoluşsal bir kabuk lehçesinde, testdosya varoluş testleri ${@+"$@"}için ve bazı eski kırık kabuklara hitap eden argümanları genişletme püf noktası (sadece "$@"bir POSIX uyumlu bir kabuğun içindeydik) hiledir .


Bir kişi, xuygun alıntılama kullanımında olsaydı , korsanlığa ihtiyaç duymazdı, çünkü bu deyimi zorunlu kılan durumlar, şimdiye kadar kullanılmayan test çağrıları ile birden fazla testi çevreleyen -aya da -obirleştiren durumları çevreliyor .
Charles Duffy

@CharlesDuffy Gerçekten; test x$whateverBen vernik içinde soğan gibi orada görünüyor işleyenler ediyorum. Kırık eski kabuğa alıntı yapmak için güvenemezsek, son ${@+"$@"}girişim anlamsızdır.
Kaz,

2

Bourne kabuğu diline ait tüm varyasyonlar Perl, Python, Ruby, node.js ve hatta (tartışmalı) Tcl gibi modern betik dillerine kıyasla nesnel olarak korkunç. Hatta biraz karmaşık bir şey yapmak zorunda kalırsanız, yukarıdakilerden birini kabuk betiği yerine kullanırsanız uzun vadede daha mutlu olursunuz.

İlk ve tek kabuk dili hala o yeni dilleri üzerinde olmasıdır avantaj şey kendisi çağıran /bin/shiddia Unix olmak herhangi bir şeyle ilgili varolmaya garantilidir. Ancak, bir şey POSIX uyumlu bile olmayabilir; eski patentli Unix'lerin birçoğu, Unix95'in (evet, Unix95, yirmi yıl önce ve saymanın) talep ettiği değişikliklerden önce/bin/sh , varsayılan PATH'taki uygulamaları ve uygulanan dili dondurdu . Orada Unix95 bir dizi olabilir veya eğer şanslıysanız bile POSIX.1-2001, bir dizindeki araçları değil varsayılan PATH üzerinde (örneğin ) ama onlar varoldukları için garanti edilmez./usr/xpg4/bin

Bununla birlikte, Perl'in temellerinin keyfi seçilmiş bir Unix kurulumunda Bash'inkinden daha fazla bulunması muhtemeldir . (Yani "Perl temelleri" ile /usr/bin/perlvar ve olan bazı Perl 5, muhtemelen oldukça eski, sürümü ve sen şanslı eğer tercüman o sürümü ile birlikte gelen bu modül seti de mevcuttur.)

Bu nedenle:

Her yerde çalışmak zorunda olan ve Unix olmasını gerektiren ("configure" komut dosyası gibi) bir şeyler yazıyorsanız, kullanmanız #! /bin/shve herhangi bir uzantı kullanmamanız gerekir. Bugünlerde bu durumda POSIX.1-2001 uyumlu bir kabuk yazacaktım, ancak birisi paslı demir için destek isterse POSIXizm'i düzeltmeye hazır olurdum.

Eğer varsa Ama değil her yerde çalışıyor olmalıdır birşeyler yazmayı, ardından an hiç bir Bashism kullanmak için cazip olan, durdurmak gerekir ve bunun yerine daha iyi bir kodlama dilinde şeyin tamamını yeniden yazmak. Gelecekteki benliğiniz size teşekkür edecek.

(Ne zaman almaktadır ? İlk sipariş için Bash uzantıları kullanılması uygun: asla ikinci sırayla için: sadece Bash interaktif bir ortam uzatmak - örneğin akıllı sekme tamamlama ve fantezi istemleri sağlamaktır..)


Geçen gün böyle bir şey yapan bir senaryo yazmak zorunda kaldım find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -. Bashis ( read -d "") ve GNU uzantıları ( find -print0, tar --null) olmadan düzgün çalışmasını sağlamak mümkün olmazdı . Perl'de bu çizgiyi yeniden uygulamak çok daha uzun ve zahmetli olur. Bu, temelleri ve POSIX dışındaki diğer uzantıları kullanırken yapılacak durumdur.
michau

@michau Artık, bu elipslerde neler olduğuna bağlı olarak artık tek bir astar olarak nitelendirilemeyebilir, ancak bence tam anlamıyla, " her şeyi daha iyi bir betik dilinde yeniden yazma " nı anlamıyorsunuz. Benim tarzım bir şeye benziyor perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmd, yalnızca temellere ihtiyaç duymakla kalmaz, herhangi bir GNU katran uzantısına ihtiyaç duymazsınız. (Komut satırı uzunluğu bir sorun tar --null -T
teşkil ediyorsa veya taramayla tar'ın

Mesele şu ki, findsenaryomda 50.000'den fazla dosya adı var, bu yüzden senaryonuz argüman uzunluğu sınırına ulaşmış olabilir. POSIX dışı uzantıları kullanmayan doğru bir uygulamanın katran çıktısını adım adım oluşturması veya Perl kodunda Archive :: Tar :: Stream kullanması gerekebilir. Tabii ki yapılabilir, ancak bu durumda Bash senaryosu yazmak için çok daha hızlıdır ve daha az boyunduruğa sahiptir ve bu nedenle hatalara daha az yer açar.
michau

BTW, hızlı ve özlü bir şekilde yerine Bash Perl kullanabilirsiniz: find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. Ama sorular şunlardır: Kullandığım 1) find -print0ve tar --nullyine bu yüzden bashism kaçınmanın ne anlamı var read -d ""tam olarak bir şey aynı tür (Perl aksine bir POSIX şey birgün hale gelebilir, o boş ayırıcı işlemek için bir GNU oluşumudur, )? 2) Perl, Bash'ten çok daha mı yaygın? Son zamanlarda Docker görüntüleri kullanıyorum ve çoğunda Bash var ancak Perl yok. Yeni senaryoların orada eski Unice'lerden daha çok çalıştırılması daha muhtemel.
michau
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.