Senaryodaki shebang satırında birden fazla olasılığa nasıl sahip olabilirim?


16

Ben teorik olarak çeşitli ortamlar (ve PATHs) ve çeşitli Linux sistemleri üzerinde çeşitli kullanıcılar tarafından çalıştırılabilir bir Python komut dosyası var ilginç bir durumdayım. Bu komut dosyası, yapay kısıtlamalar olmadan mümkün olduğunca çok üzerinde çalıştırılabilir olmasını istiyorum. İşte bilinen bazı kurulumlar:

  • Python 2.6 sistem Python sürümüdür, bu nedenle python, python2 ve python2.6 / usr / bin dosyasında bulunur (ve eşdeğerdir).
  • Python 2.6, yukarıdaki gibi sistem Python sürümüdür, ancak Python 2.7 yanında python2.7 olarak yüklenir.
  • Python 2.4 betiğimin desteklemediği sistem Python sürümüdür. / Usr / bin dizininde eşdeğer olan python, python2 ve python2.4 ve betiğin desteklediği python2.5 var.

Bunların üçünde de aynı yürütülebilir python komut dosyasını çalıştırmak istiyorum. İlk önce /usr/bin/python2.7 kullanmaya çalışsaydı, eğer varsa /usr/bin/python2.6'ya, ardından /usr/bin/python2.5'e geri düşse, bunlardan hiçbiri yoksa hata yap. Bununla birlikte, eğer mevcutsa doğru tercümanlardan birini bulabildiği sürece, mümkün olan en son 2.x'i kullanarak fazla takılmadım.

İlk eğim, shebang hattını değiştirmekti:

#!/usr/bin/python

için

#!/usr/bin/python2.[5-7]

çünkü bu bash'da iyi çalışıyor. Ancak betiği çalıştırmak:

/usr/bin/python2.[5-7]: bad interpreter: No such file or directory

Tamam, bu yüzden bash'da da çalışan aşağıdakileri deniyorum:

#!/bin/bash -c /usr/bin/python2.[5-7]

Ama yine, bu başarısız olur:

/bin/bash: - : invalid option

Tamam, tabii ki doğru tercümanı bulan ve bulduğu herhangi bir tercümanı kullanarak python komut dosyasını çalıştıran ayrı bir kabuk komut dosyası yazabilirim. Ben sadece en güncel python 2 yorumlayıcı yüklü olarak çalıştırıldığı sürece yeterli olması gereken iki dosyayı dağıtmak için bir güçlük bulurdum. İnsanlardan tercümanı açıkça çağırmasını istemek (örn. $ python2.5 script.py) Bir seçenek değildir. Kullanıcının PATH'in belirli bir şekilde kurulmasına güvenmek de bir seçenek değildir.

Düzenle:

Python komut içinde kontrol Sürüm edilir değil Python 2.6 itibariyle mevcut (ve 2.5 ile kullanılabilir "ile" deyimi kullanıyorum beri işe gidiş from __future__ import with_statement). Bu, komut dosyasının kullanıcı dostu olmayan bir SyntaxError ile hemen başarısız olmasına neden olur ve ilk önce sürümü kontrol etme ve uygun bir hata yayınlama fırsatım olmasını engeller.

Örnek: (bunu 2.6'dan küçük bir Python yorumlayıcıyla deneyin)

#!/usr/bin/env python

import sys

print "You'll never see this!"
sys.exit()

with open('/dev/null', 'w') as out:
    out.write('something')

Gerçekten ne istediğinizi değil, yorum için. Ancak import sys; sys.version_info(), kullanıcının gerekli python sürümüne sahip olup olmadığını kontrol etmek için kullanabilirsiniz .
Bernhard

2
@Bernhard Evet bu doğru, ama o zamana kadar bu konuda bir şey yapmak için çok geç. Komut dosyasını doğrudan çalıştırarak yukarıda listelediğim üçüncü durum için (yani ./script.py) python2.4'ün çalıştırılmasına neden olur, bu da kodunuzun yanlış sürüm olduğunu algılamasına neden olur (ve muhtemelen çıkılır). Ama bunun yerine tercüman olarak kullanılabilecek mükemmel bir python2.5 var!
user108471

2
Uygun bir python olup olmadığını anlamak için bir sarıcı komut dosyası kullanın ve execvarsa, bir hata yazdırın.
Kevin

1
Bu yüzden aynı dosyada ilk şey yapın.
Kevin

9
@ user108471: Mesele hattının bash tarafından ele alındığını varsayıyorsunuz. Bu bir sistem çağrısı ( execve) değildir. Argümanlar string değişmezleri, globbing yok, regexps yok. Bu kadar. İlk arg "/ bin / bash" ve ikinci seçenekler ("-c ...") bile bu seçenekler bir kabuk tarafından ayrıştırılmaz. Bash yürütülebilir dosyasına işlenmemiş olarak teslim edilirler, bu yüzden bu hataları alırsınız. Ayrıca, shebang sadece başlangıçta ise çalışır. Yani burada şanssızsınız, korkuyorum (bir python tercümanı bulan ve korkunç bir karmaşaya benzeyen bir HERE dokümanı besleyen bir senaryo kısa).
goldilocks

Yanıtlar:


13

Uzman değilim, ancak kullanmak için tam bir python sürümü belirtmemeli ve bu seçeneği sisteme / kullanıcıya bırakmamalısınız.

Ayrıca bunu kodda python için hardcoding yolu yerine kullanmalısınız:

#!/usr/bin/env python

veya

#!/usr/bin/env python3 (or python2)

Bu edilir önerilen tarafından Python doc tüm sürümlerinde:

İyi bir seçim genellikle

#!/usr/bin/env python

tüm PATH'de Python yorumlayıcısını arar. Ancak, bazı Unices env komutuna sahip olmayabilir, bu nedenle yorumlayıcı yolu olarak hardcode / usr / bin / python kodlaması yapmanız gerekebilir.

Çeşitli dağıtımlarda Python farklı yerlere kurulabilir, bu yüzden envonu arayacaktır PATH. Tüm büyük Linux dağıtımlarında ve FreeBSD'de gördüklerimde mevcut olmalıdır.

Komut dosyası, PATH'nizde bulunan ve dağıtımınız tarafından seçilen Python sürümüyle yürütülmelidir *.

Betiğiniz 2.4 dışındaki tüm Python sürümleriyle uyumluysa, sadece Python 2.4'te çalıştırılıp çalıştırılmadığını kontrol etmeli ve bazı bilgileri yazdırıp çıkmalısınız.

Okunacak daha fazla bilgi

  • Burada Python'un farklı sistemlere hangi yerlerde kurulabileceğine dair örnekler bulabilirsiniz.
  • Burada kullanım için bazı avantajlar ve dezavantajlar bulabilirsiniz env.
  • Burada PATH manipülasyonu örnekleri ve farklı sonuçlar bulabilirsiniz.

dipnot

* Gentoo'da bir araç var eselect. Bunu kullanarak farklı uygulamaların (Python dahil) varsayılan sürümlerini varsayılan olarak ayarlayabilirsiniz:

$ eselect python list
Available Python interpreters:
  [1]   python2.6
  [2]   python2.7 *
  [3]   python3.2
$ sudo eselect python set 1
$ eselect python list
Available Python interpreters:
  [1]   python2.6 *
  [2]   python2.7
  [3]   python3.2

2
Yapmak istediğim şeyin iyi uygulama olarak kabul edilebilecek şeylere karşı olduğunu takdir ediyorum. Gönderdiğiniz şey tamamen makul, ancak aynı zamanda istediğim şey de değil. Benim umurumda her durumda Python uygun sürümünü tespit etmek mümkün olduğunda benim kullanıcıları açıkça Python uygun sürümüne benim script işaret etmek istemiyorum.
user108471

1
Lütfen "Python 2.4'de çalıştırılıp içeri girmediğini ve bazı bilgileri yazdırıp çıkabildiğini" neden kontrol edemediğime ilişkin güncellememe bakın.
user108471

Haklısın. Ben sadece SO üzerinde bu soruyu buldum ve şimdi sadece bir dosyaya sahip olmak istiyorsanız bunu yapmak için bir seçenek olmadığını görebilirsiniz ...
pbm

9

Birkaç yorumdan bazı fikirlere dayanarak, gerçekten çirkin bir kesmek gibi birlikte çalışmayı başardım. Betik bash betiği bir Python betiğini sarar ve bir "burada belge" aracılığıyla Python yorumlayıcısına iletir.

Başlangıçta:

#!/bin/bash

''':'
vers=( /usr/bin/python2.[5-7] )
latest="${vers[$((${#vers[@]} - 1))]}"
if !(ls $latest &>/dev/null); then
    echo "ERROR: Python versions < 2.5 not supported"
    exit 1
fi
cat <<'# EOF' | exec $latest - "$@"
''' #'''

Python kodu buraya gelir. Sonra en sonunda:

# EOF

Kullanıcı komut dosyasını çalıştırdığında, komut dosyasının geri kalanını burada bir belge olarak yorumlamak için 2.5 ile 2.7 arasındaki en son Python sürümü kullanılır.

Bazı maskaralıklara ilişkin açıklama:

Eklediğim üçlü alıntılar da aynı komut dosyasının bir Python modülü olarak içe aktarılmasına izin veriyor (test amacıyla kullanıyorum). Python tarafından içe aktarıldığında, birinci ve ikinci üçlü-tek tırnak arasındaki her şey modül düzeyinde bir dize olarak yorumlanır ve üçüncü üçlü-tek tırnak alıntılanır. Gerisi sıradan Python.

Doğrudan çalıştırıldığında (şimdi bir bash betiği olarak), ilk iki tek tırnak boş bir dize olur ve üçüncü tek tırnak, yalnızca iki nokta üst üste içeren dördüncü tek tırnaklı başka bir dize oluşturur. Bu dize Bash tarafından işlem yapılmaz olarak yorumlanır. Diğer her şey, / usr / bin içindeki Python ikili dosyalarını globlamak, sonuncuyu seçmek ve exec çalıştırmak, dosyanın geri kalanını burada belge olarak geçirmek için Bash sözdizimidir. Bu belge, yalnızca bir karma / pound / octothorpe işareti içeren bir Python üçlü-tek tırnakla başlar. Komut dosyasının geri kalanı '# EOF' yazan satır bu belgeyi sonlandırana kadar normal olarak yorumlanır.

Bunun sapkın olduğunu hissediyorum, umarım birisinin daha iyi bir çözümü vardır.


Belki sonuçta o kadar kötü değil;) +1
goldilocks

Bunun dezavantajı, çoğu editörde sözdizimi renklendirmesini bozmasıdır
Lie Ryan

@LieRyan Bu duruma bağlı. Komut dosyam, çoğu metin düzenleyicisinin renklendirme için hangi sözdizimini kullanacağını seçerken tercih ettiği bir .py dosya adı uzantısı kullanıyor. Ben .py uzantısı olmadan bu adlandırmak olsaydı Varsayalım, ben böyle bir şeyle (en azından Vim kullanıcılar için) doğru sözdizimi ipucu için kipsatırını kullanabilirsiniz: # ft=python.
user108471

7

Mesele hattı sadece bir tercümana giden sabit bir yol belirleyebilir. #!/usr/bin/envTercümana bakma hilesi var PATHama hepsi bu. Daha fazla karmaşıklık istiyorsanız, bazı sarıcı kabuk kodu yazmanız gerekir.

En belirgin çözüm bir sarıcı komut dosyası yazmaktır. Python komut dosyasını çağırın foo.realve bir sarıcı komut dosyası oluşturun foo:

#!/bin/sh
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi

Her şeyi bir dosyaya koymak istiyorsanız, genellikle bir satırla başlayan (yani kabuk tarafından yürütülecek) bir çokgrup yapabilirsiniz #!/bin/sh, ancak aynı zamanda başka bir dilde geçerli bir komut dosyasıdır. Dile bağlı olarak, bir çoklu dil imkansız olabilir ( #!örneğin bir sözdizimi hatasına neden olursa ). Python'da çok zor değil.

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi
'''
# real Python script starts here
def …

(Arasındaki tüm metin '''ve '''hiçbir etkisi olmaz toplevel, bir Python dizedir. Kabuk, ikinci çizgi ''':'sıyırma tırnak sonra hiç-op komutu olan :.)


İkinci çözüm, bu cevapta olduğu# EOF gibi sonunda eklemeyi gerektirmediği için güzeldir . Yaklaşımınız temel olarak burada ana hatlarıyla aynıdır .
sschuberth

6

Gereksinimleriniz bilinen bir ikili dosyalar listesini belirttiği için, Python'da aşağıdakileri yapabilirsiniz. Python'un tek haneli küçük / büyük bir versiyonunu geçmeyecekti ama yakın zamanda bunun olacağını görmüyorum.

İkili etiketli sürüm python yürütme geçerli sürümünden daha yüksekse, diskte bulunan en yüksek sürümü, sıralı pitonlar listesinden artan çalışır. "Sıralı artan sürüm listesi" bu kod için önemli bir bittir.

#!/usr/bin/env python
import os, sys

pythons = [ '/usr/bin/python2.3','/usr/bin/python2.4', '/usr/bin/python2.5', '/usr/bin/python2.6', '/usr/bin/python2.7' ]
py = list(filter( os.path.isfile, pythons ))
if py:
  py = py.pop()
  thepy = int( py[-3:-2] + py[-1:] )
  mypy  = int( ''.join( map(str, sys.version_info[0:2]) ) )
  if thepy > mypy:
    print("moving versions to "+py)
    args = sys.argv
    args.insert( 0, sys.argv[0] )
    os.execv( py, args )

print("do normal stuff")

Benim cızırtılı piton için özür dilerim


uyguladıktan sonra çalışmaya devam etmez mi? yani normal şeyler iki kez idam olur?
Janus Troelsen

1
execv, yürütülmekte olan programı yeni yüklenen bir program görüntüsüyle değiştirir
Matt

Bu, bulduğumdan daha az çirkin hisseden harika bir çözüm gibi görünüyor. Bunun bu amaç için işe yarayıp yaramadığını görmek zorundayım.
user108471

Bu öneri neredeyse ihtiyacım olan şey için işe yarıyor. Tek kusur aslında bahsetmediğim bir şey: Python 2.5'i desteklemek için from __future__ import with_statement, Python betiğindeki ilk şey olması gereken kullanıyorum . Yeni tercümanı başlatırken bu eylemi gerçekleştirmenin bir yolunu bildiğinizi sanmıyorum?
user108471

Eğer olması gerekenden de emin olan çok ilk şey nedir? veya withnormal bir içe aktarma gibi herhangi bir s kullanmayı denemeden hemen önce . Ekstra if mypy == 25: from __future__ import with_statement'normal eşyalardan' önce işe yarıyor mu? 2.4'ü desteklemiyorsanız muhtemelen if'e ihtiyacınız yoktur.
Matt

0

Kullanılabilir phython yürütülebilir dosyasını kontrol eden ve komut dosyasıyla parametre olarak çağıran küçük bir bash betiği yazabilirsiniz. Daha sonra bu komut dosyasını shebang satır hedefi yapabilirsiniz:

#!/my/python/search/script

Ve bu komut dosyası basitçe yapar (aramadan sonra):

"$python_path" "$1"

Çekirdeğin bu betik dolaylamasını kabul edip etmeyeceğinden emin değildim ama kontrol ettim ve işe yarıyor.

Düzenle 1

Bu utanç verici algıyı nihayet iyi bir teklif haline getirmek için:

Her iki komut dosyasını tek bir dosyada birleştirmek mümkündür. Python betiğini bash betiğinde burada belge olarak yazmanız yeterlidir (python betiğini değiştirirseniz betiği tekrar birlikte kopyalamanız gerekir). Ya örneğin / tmp'de geçici bir dosya oluşturursunuz veya (python bunu destekliyorsa, bilmiyorum) komut dosyasını tercümana girdi olarak sağlarsınız:

# do the search here and then
# either
cat >"tmpfile" <<"EOF" # quoting EOF is important so that bash leaves the python code alone
# here is the python script
EOF
"$python_path" "tmpfile"
# or
"$python_path" <<"EOF"
# here is the python script
EOF

Bu, qeustion'un son paragrafında zaten belirtilmiş olan çözümdür!
Bernhard

Zeki, ancak bu sihirli komut dosyasının her sistemde bir yere kurulmasını gerektirir.
user108471

@ Bernhard Oooops, yakalandı. Gelecekte sonuna kadar okuyacağım. Tazminat olarak bunu tek bir dosya çözümüne geliştireceğim.
Hauke ​​Laging

@ user108471 Sihirli komut dosyası böyle bir şey içerebilir: $(ls /usr/bin/python?.? | tail -n1 )ama bunu akıllıca bir shebang'da kullanmayı başaramadım.
Bernhard

@Bernhard Aramayı shebang hattı içinde mi yapmak istiyorsunuz? Çekirdek IIRC, shebang satırında alıntı yapmak umurumda değil. Aksi takdirde (eğer bu arada değiştiyse) `#! / Bin / bash -c do_search_here_without_whitespace ...; exec $ python" $ 1 "gibi bir şey yapılabilir.
Hauke ​​Laging
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.