ฉันจะมีความเป็นไปได้มากกว่าหนึ่งรายการในกลุ่ม Shebang ของสคริปต์ได้อย่างไร


16

ฉันอยู่ในสถานการณ์ที่น่าสนใจเล็กน้อยซึ่งฉันมีสคริปต์ Python ที่สามารถเรียกใช้โดยผู้ใช้หลายคนที่มีสภาพแวดล้อมหลากหลาย (และ PATH) และในระบบ Linux ที่หลากหลาย ฉันต้องการให้สคริปต์นี้สามารถใช้งานได้กับสิ่งเหล่านี้ให้มากที่สุดโดยไม่มีข้อ จำกัด นี่คือการตั้งค่าที่รู้จักบางส่วน:

  • Python 2.6 เป็นรุ่น Python ของระบบดังนั้น python, python2 และ python2.6 มีอยู่ใน / usr / bin (และเทียบเท่า)
  • Python 2.6 เป็นรุ่น Python ของระบบดังกล่าวข้างต้น แต่ Python 2.7 ได้รับการติดตั้งไว้ข้างๆเป็น python2.7
  • Python 2.4 เป็นรุ่น Python ของระบบซึ่งสคริปต์ของฉันไม่รองรับ ใน / usr / bin เรามี python, python2 และ python2.4 ซึ่งเทียบเท่าและ python2.5 ซึ่งสคริปต์สนับสนุน

ฉันต้องการเรียกใช้สคริปต์หลามปฏิบัติการที่เหมือนกันในทั้งสามนี้ มันจะดีถ้าพยายามใช้ /usr/bin/python2.7 ก่อนถ้ามีอยู่ให้ถอยกลับไปที่ /usr/bin/python2.6 จากนั้นถอยกลับไปที่ /usr/bin/python2.5 จากนั้น ข้อผิดพลาดเพียงถ้าไม่มีสิ่งเหล่านี้อยู่ ฉันไม่ได้วางสายโดยใช้ 2.x ที่เป็นไปได้ล่าสุดตราบใดที่สามารถหาล่ามที่ถูกต้องได้หากมี

ความชอบครั้งแรกของฉันคือการเปลี่ยนแนว shebang จาก:

#!/usr/bin/python

ถึง

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

ตั้งแต่นี้ทำงานได้ดีในทุบตี แต่การเรียกใช้สคริปต์ให้:

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

โอเคดังนั้นฉันลองทำสิ่งต่อไปนี้ซึ่งใช้ได้ใน bash:

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

แต่สิ่งนี้ล้มเหลวด้วย:

/bin/bash: - : invalid option

ตกลงเห็นได้ชัดว่าฉันสามารถเขียนเชลล์สคริปต์แยกต่างหากที่พบล่ามที่ถูกต้องและรันสคริปต์ไพ ธ อนโดยใช้ล่ามอะไรก็ได้ที่พบ ฉันแค่พบว่ามันเป็นเรื่องยุ่งยากที่จะแจกจ่ายสองไฟล์โดยที่ไฟล์ควรจะพอเพียงตราบใดที่มันรันด้วยล่าม python 2 ที่ทันสมัยที่สุด การขอให้คนเรียกล่ามอย่างชัดเจน (เช่น$ python2.5 script.py) ไม่ได้เป็นตัวเลือก การใช้ PATH ของผู้ใช้ในการตั้งค่าวิธีการบางอย่างนั้นก็ไม่ใช่ตัวเลือก

แก้ไข:

การตรวจสอบเวอร์ชันภายในสคริปต์ Python จะไม่ทำงานเนื่องจากฉันใช้คำสั่ง "with" ซึ่งมีอยู่ตั้งแต่ Python 2.6 (และสามารถใช้ใน 2.5 ด้วยfrom __future__ import with_statement) สิ่งนี้ทำให้สคริปต์ล้มเหลวทันทีด้วย SyntaxError ที่ผู้ใช้ไม่เป็นมิตรและป้องกันไม่ให้ฉันมีโอกาสตรวจสอบเวอร์ชันก่อนและปล่อยข้อผิดพลาดที่เหมาะสม

ตัวอย่าง: (ลองใช้ตัวแปลภาษา Python น้อยกว่า 2.6)

#!/usr/bin/env python

import sys

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

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

ไม่ใช่สิ่งที่คุณต้องการจริงๆ แต่คุณสามารถใช้import sys; sys.version_info()เพื่อตรวจสอบว่าผู้ใช้มีเวอร์ชันหลามที่ต้องการหรือไม่
Bernhard

2
@ แบร์นฮาร์ดใช่มันเป็นเรื่องจริง แต่แล้วมันก็สายเกินไปที่จะทำอะไรเกี่ยวกับมัน สำหรับสถานการณ์ที่สามที่ฉันทำรายการไว้ข้างต้นการเรียกใช้สคริปต์โดยตรง (เช่น./script.py) จะทำให้ python2.4 เรียกใช้งานซึ่งจะทำให้โค้ดของคุณตรวจพบว่าเป็นเวอร์ชันที่ไม่ถูกต้อง (และออกจากสมมุติ) แต่มี python2.5 ที่ดีอย่างสมบูรณ์ที่สามารถใช้เป็นล่ามแทนได้!
user108471

2
ใช้สคริปต์ตัวตัดคำเพื่อตรวจสอบว่ามีไพ ธ อนที่เหมาะสมหรือexecไม่ถ้าใช่มิฉะนั้นจะพิมพ์ข้อผิดพลาด
เควิน

1
ดังนั้นทำสิ่งแรกในไฟล์เดียวกัน
เควิน

9
@ user108471: คุณกำลังสันนิษฐานว่าบรรทัด shebang ได้รับการจัดการโดย bash มันไม่ใช่มันเป็นการเรียกของระบบ ( execve) ข้อโต้แย้งเป็นตัวอักษรสตริงไม่มี globbing ไม่มี regexps แค่นั้นแหละ. แม้ว่า arg แรกคือ "/ bin / bash" และตัวเลือกที่สอง ("-c ... ") ตัวเลือกเหล่านั้นจะไม่ถูกแยกวิเคราะห์โดยเชลล์ พวกมันจะถูกส่งไปยัง bash executable ซึ่งเป็นสาเหตุที่ทำให้คุณได้รับข้อผิดพลาดเหล่านั้น นอกจากนี้ shebang ยังทำงานได้ก็ต่อเมื่อเริ่มต้นเท่านั้น ดังนั้นคุณจะโชคดีที่นี่ฉันกลัว (สั้น ๆ ของสคริปต์ที่พบล่ามหลามและป้อนมันที่นี่หมอเอกสารซึ่งฟังดูเหมือนเป็นระเบียบอันยิ่งใหญ่)
goldilocks

คำตอบ:


13

ฉันไม่ใช่ผู้เชี่ยวชาญ แต่ฉันเชื่อว่าคุณไม่ควรระบุรุ่นหลามที่แน่นอนที่จะใช้และปล่อยให้ตัวเลือกนั้นเป็นระบบ / ผู้ใช้

นอกจากนี้คุณควรใช้สิ่งนั้นแทนเส้นทาง hardcoding ไปยัง python ในสคริปต์:

#!/usr/bin/env python

หรือ

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

มันถูกแนะนำ โดย งูหลาม docในทุกรุ่น:

ทางเลือกที่ดีมักจะเป็น

#!/usr/bin/env python

ซึ่งค้นหาตัวแปล Python ใน PATH ทั้งหมด อย่างไรก็ตาม Unices บางอันอาจไม่มีคำสั่ง env ดังนั้นคุณอาจต้อง hardcode / usr / bin / python เป็นเส้นทางล่าม

ในการกระจายต่างๆงูใหญ่อาจจะติดตั้งในสถานที่ที่แตกต่างกันดังนั้นจะค้นหามันในenv PATHมันควรจะมีอยู่ในลีนุกซ์รุ่นใหญ่ ๆ และจากสิ่งที่ฉันเห็นใน FreeBSD

สคริปต์ควรดำเนินการกับ Python เวอร์ชันนั้นซึ่งอยู่ใน PATH ของคุณและเลือกโดยการแจกจ่าย * ของคุณ

หากสคริปต์ของคุณเข้ากันได้กับ Python ทุกรุ่นยกเว้น 2.4 คุณควรตรวจสอบข้างในถ้าสคริปต์นั้นทำงานใน Python 2.4 แล้วพิมพ์ข้อมูลและออก

อ่านเพิ่มเติม

  • ที่นี่คุณสามารถหาตัวอย่างได้ว่าติดตั้ง Python ในตำแหน่งใดในระบบที่ต่างกัน
  • ที่นี่envคุณสามารถหาข้อดีและข้อเสียในการใช้
  • ที่นี่คุณสามารถค้นหาตัวอย่างของการจัดการเส้นทางและผลลัพธ์ที่แตกต่าง

เชิงอรรถ

* ใน Gentoo eselectมีเครื่องมือที่เรียกว่า การใช้งานคุณสามารถตั้งค่าเวอร์ชันเริ่มต้นของแอพพลิเคชั่นต่างๆ (รวมถึง Python) เป็นค่าเริ่มต้น:

$ 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
ฉันขอขอบคุณที่สิ่งที่ฉันต้องการจะทำคือการต่อต้านการปฏิบัติที่ดี สิ่งที่คุณโพสต์นั้นสมเหตุสมผลทั้งหมด แต่ในขณะเดียวกันมันก็ไม่ใช่สิ่งที่ฉันขอ ฉันไม่ต้องการให้ผู้ใช้ของฉันต้องชี้สคริปต์ของฉันไปที่เวอร์ชันที่เหมาะสมของ Python เมื่อเป็นไปได้อย่างสมบูรณ์แบบในการตรวจสอบเวอร์ชันของ Python ที่เหมาะสมในทุกสถานการณ์ที่ฉันสนใจ
user108471

1
โปรดดูการอัปเดตของฉันสำหรับสาเหตุที่ฉันไม่สามารถ "ตรวจสอบภายในถ้ามันทำงานใน Python 2.4 และพิมพ์ข้อมูลและออก"
user108471

คุณพูดถูก ฉันเพิ่งพบนี้คำถามเกี่ยวกับ SO และตอนนี้ฉันจะเห็นว่ามีตัวเลือกที่จะทำถ้าคุณต้องการที่จะมีเพียงหนึ่งไฟล์ ...
PBM

9

จากแนวคิดบางประการจากความคิดเห็นเล็กน้อยฉันจัดการกับการแฮ็กข้อมูลแฮ็คที่น่าเกลียดที่ดูเหมือนว่าจะทำงานได้ดี สคริปต์กลายเป็นสคริปต์ทุบตีล้อมสคริปต์ Python และส่งไปยังล่าม Python ผ่าน "เอกสารที่นี่"

ที่จุดเริ่มต้น:

#!/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 - "$@"
''' #'''

รหัสไพ ธ อนอยู่ที่นี่ จากนั้นในตอนท้าย:

# EOF

เมื่อผู้ใช้เรียกใช้สคริปต์เวอร์ชัน Python ล่าสุดระหว่าง 2.5 และ 2.7 จะถูกใช้เพื่อตีความส่วนที่เหลือของสคริปต์เป็นเอกสารที่นี่

คำอธิบายเกี่ยวกับ shenanigans:

สิ่งที่อ้างอิงสามชั้นที่ฉันเพิ่มยังทำให้สคริปต์เดียวกันนี้สามารถนำเข้าเป็นโมดูล Python (ซึ่งฉันใช้สำหรับการทดสอบ) เมื่อนำเข้าโดย Python ทุกอย่างระหว่างตัวแรกและตัวที่สองสามคำพูดถูกตีความว่าเป็นสตริงระดับโมดูลและสามตัวที่อ้างถึงเดี่ยวถูกคอมเม้นท์ ส่วนที่เหลือเป็น Python สามัญ

เมื่อเรียกใช้โดยตรง (เป็นสคริปต์ทุบตีตอนนี้) เครื่องหมายคำพูดเดี่ยวสองรายการแรกกลายเป็นสตริงว่างและเครื่องหมายคำพูดเดี่ยวครั้งที่สามจัดเป็นสตริงอื่นด้วยเครื่องหมายคำพูดเดี่ยวลำดับที่สี่มีเพียงเครื่องหมายโคลอน สตริงนี้ตีความโดย Bash ว่าไม่มีตัวเลือก ทุกอย่างอื่นคือ Bash syntax สำหรับ globbing ไพ ธ อนใน / usr / bin เลือกอันสุดท้ายและรัน exec ผ่านไฟล์ที่เหลือเป็นเอกสารที่นี่ เอกสารที่นี่เริ่มต้นด้วยการเสนอราคาสามครั้งเดียวของ Python ที่มีเฉพาะเครื่องหมาย hash / pound / octothorpe ส่วนที่เหลือของสคริปต์จะถูกตีความตามปกติจนกว่าการอ่านบรรทัด '# EOF' จะยุติเอกสารที่นี่

ฉันรู้สึกว่านี่เป็นสิ่งผิดปกติดังนั้นฉันหวังว่าบางคนจะมีทางออกที่ดีกว่า


อาจจะไม่เป็นที่น่ารังเกียจนัก) +1
goldilocks

ข้อเสียของสิ่งนี้คือมันจะทำให้สีของไวยากรณ์ผิดไปจากบรรณาธิการส่วนใหญ่
Lie Ryan

@LieRyan ที่ขึ้นอยู่กับ สคริปต์ของฉันใช้นามสกุลไฟล์. py ซึ่งโปรแกรมแก้ไขข้อความส่วนใหญ่ชอบเมื่อเลือกไวยากรณ์ที่จะใช้สำหรับการระบายสี สมมุติฐานถ้าผมจะเปลี่ยนชื่อนี้โดยการขยาย .py ผมสามารถใช้ modeline ให้คำแนะนำไวยากรณ์ที่ถูกต้อง (สำหรับผู้ใช้ที่เป็นกลุ่มอย่างน้อย) # ft=pythonกับสิ่งที่ต้องการ:
user108471

7

บรรทัด shebang สามารถระบุเส้นทางที่แน่นอนไปยังล่ามเท่านั้น มี#!/usr/bin/envเคล็ดลับในการค้นหาล่ามในตัวPATHแต่นั่นมัน หากคุณต้องการความซับซ้อนมากขึ้นคุณจะต้องเขียนโค้ดของ wrapper shell

ทางออกที่ชัดเจนที่สุดคือการเขียนสคริปต์ wrapper โทรสคริปต์ไพ ธ อนfoo.realและสร้างสคริปต์ตัวคลุมfoo:

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

หากคุณต้องการใส่ทุกอย่างไว้ในไฟล์เดียวคุณสามารถทำให้เป็นหลายภาษาที่ขึ้นต้นด้วย#!/bin/shบรรทัด (เชลล์จะถูกประหารชีวิตดังนั้น) แต่ก็เป็นสคริปต์ที่ถูกต้องในภาษาอื่น อาจมีหลายภาษาที่เป็นไปไม่ได้ (ขึ้นอยู่กับภาษา) (หาก#!เป็นสาเหตุของข้อผิดพลาดทางไวยากรณ์) ใน Python มันไม่ยากมาก

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

(ข้อความทั้งหมดระหว่าง'''และ'''เป็นสตริง Python ที่ระดับบนสุดซึ่งไม่มีผลกระทบใด ๆ สำหรับเชลล์บรรทัดที่สองคือ''':'ซึ่งหลังจากคำพูดการแยกเป็นคำสั่ง no-op :)


คำตอบที่สองนั้นดีเพราะมันไม่จำเป็นต้องเพิ่ม# EOFในตอนท้ายเหมือนในคำตอบนี้ วิธีการของคุณโดยทั่วไปจะเหมือนกันตามที่ระบุไว้ที่นี่
sschuberth

6

เนื่องจากความต้องการของคุณระบุรายชื่อไบนารีที่คุณสามารถทำได้ใน Python ดังต่อไปนี้ มันจะไม่ทำงานผ่าน Python รุ่นรอง / หลักรุ่นเดียว แต่ฉันไม่เห็นว่าจะเกิดขึ้นได้ตลอดเวลาเร็ว ๆ นี้

รันเวอร์ชันสูงสุดที่อยู่บนดิสก์จากรายการสั่งซื้อที่เพิ่มขึ้นของงูเหลือมเวอร์ชันหากเวอร์ชันที่ติดแท็กบนไบนารีนั้นสูงกว่าเวอร์ชันปัจจุบันของการดำเนินการ python "สั่งซื้อรายการรุ่นที่เพิ่มขึ้น" เป็นบิตที่สำคัญสำหรับรหัสนี้

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

ขอโทษสำหรับงูหลามเกาของฉัน


มันจะไม่ทำงานต่อไปหลังจากประมวลผลหรือไม่ ดังนั้นสิ่งปกติจะถูกประหารสองครั้ง?
Janus Troelsen

1
execv แทนที่โปรแกรมที่กำลังดำเนินการอยู่ในปัจจุบันด้วยอิมเมจโปรแกรมที่เพิ่งโหลดใหม่
Matt

ดูเหมือนว่าโซลูชันที่ยอดเยี่ยมที่ให้ความรู้สึกน่าเกลียดน้อยกว่าที่ฉันคิดไว้ ฉันจะต้องลองดูว่ามันใช้ได้กับจุดประสงค์นี้หรือไม่
user108471

ข้อเสนอแนะนี้เกือบจะทำงานให้กับสิ่งที่ฉันต้องการ ข้อบกพร่องเพียงอย่างเดียวคือสิ่งที่ฉันไม่ได้กล่าวถึงในตอนแรก: เพื่อรองรับ Python 2.5 ฉันใช้from __future__ import with_statementซึ่งจะต้องเป็นสิ่งแรกในสคริปต์ Python ฉันไม่คิดว่าคุณจะรู้วิธีการกระทำนั้นเมื่อเริ่มล่ามใหม่
user108471

คุณแน่ใจว่าจะต้องเป็นอย่างมากสิ่งแรก? หรือก่อนที่คุณจะลองและใช้สิ่งใด ๆwithที่เหมือนกับการนำเข้าปกติ มีการif mypy == 25: from __future__ import with_statementทำงานพิเศษ ก่อน 'สิ่งปกติ' หรือไม่? คุณอาจไม่จำเป็นต้องใช้ถ้าหากคุณไม่สนับสนุน 2.4
แมตต์

0

คุณสามารถเขียนสคริปต์ทุบตีเล็ก ๆ ซึ่งตรวจสอบว่าปฏิบัติการที่มีอยู่ของ phython และเรียกมันด้วยสคริปต์เป็นพารามิเตอร์ จากนั้นคุณสามารถกำหนดให้สคริปต์นี้เป็นเป้าหมายบรรทัด shebang:

#!/my/python/search/script

และสคริปต์นี้ก็ทำ (หลังจากการค้นหา):

"$python_path" "$1"

ฉันไม่แน่ใจว่าเคอร์เนลจะยอมรับการเปลี่ยนทิศทางสคริปต์นี้ แต่ฉันตรวจสอบและใช้งานได้

แก้ไข 1

ในการทำให้ข้อเสนอที่ดีนี้เป็นข้อเสนอที่ดีในที่สุด:

เป็นไปได้ที่จะรวมสคริปต์ทั้งสองไว้ในไฟล์เดียว คุณเพิ่งเขียนสคริปต์ python เป็นเอกสาร here ในสคริปต์ bash (ถ้าคุณเปลี่ยนสคริปต์ python คุณเพียงแค่คัดลอกสคริปต์เข้าด้วยกันอีกครั้ง) ไม่ว่าคุณจะสร้างไฟล์ชั่วคราวเช่น / tmp หรือ (ถ้าไพ ธ อนสนับสนุนผมก็ไม่รู้) คุณให้สคริปต์เป็นข้อมูลป้อนเข้าสำหรับล่าม:

# 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

นี่เป็นวิธีแก้ปัญหาที่ระบุไว้แล้วในย่อหน้าสุดท้ายของ qeustion!
Bernhard

ฉลาด แต่มันต้องการสคริปต์เวทย์มนตร์นี้เพื่อติดตั้งที่ไหนสักแห่งในทุกระบบ
user108471

@Bernhard Oooops ถูกจับ ในอนาคตฉันจะอ่านจนจบ เป็นการชดเชยฉันจะปรับปรุงให้เป็นโซลูชันไฟล์เดียว
Hauke ​​Laging

@ user108471 Magic script สามารถมีบางสิ่งเช่นนี้: $(ls /usr/bin/python?.? | tail -n1 )แต่ฉันไม่ได้ใช้มันอย่างชาญฉลาดใน Shebang
Bernhard

@Bernhard คุณต้องการค้นหาภายในบรรทัด shebang หรือไม่ IIRC เคอร์เนลไม่สนใจเกี่ยวกับการอ้างอิงในบรรทัด shebang ไม่เช่นนั้น (จากที่มีการเปลี่ยนแปลงในขณะนี้) เราสามารถทำอะไรบางอย่างเช่น `#! / bin / bash -c do_search_here_without_whitespace ... ; exec $ python" $ 1 "แต่จะทำอย่างไรถ้าไม่มีช่องว่าง?
Hauke ​​Laging
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.