Làm cách nào tôi có thể có nhiều hơn một khả năng trong dòng shebang của tập lệnh?


15

Tôi đang ở trong một tình huống thú vị khi tôi có một tập lệnh Python có thể được điều hành theo lý thuyết bởi nhiều người dùng với nhiều môi trường (và PATH) và trên nhiều hệ thống Linux. Tôi muốn kịch bản này có thể được thực thi trên càng nhiều trong số này càng tốt mà không bị hạn chế giả tạo. Dưới đây là một số thiết lập được biết đến:

  • Python 2.6 là phiên bản Python của hệ thống, vì vậy python, python2 và python2.6 đều tồn tại trong / usr / bin (và tương đương).
  • Python 2.6 là phiên bản Python của hệ thống, như trên, nhưng Python 2.7 được cài đặt cùng với nó là python2.7.
  • Python 2.4 là phiên bản Python hệ thống, mà kịch bản của tôi không hỗ trợ. Trong / usr / bin, chúng ta có python, python2 và python2.4 tương đương và python2.5, mà script hỗ trợ.

Tôi muốn chạy cùng một kịch bản python thực thi trên cả ba. Sẽ thật tuyệt nếu nó đã thử sử dụng /usr/bin/python2.7 trước, nếu nó tồn tại, sau đó quay lại /usr/bin/python2.6, sau đó quay lại /usr/bin/python2.5, sau đó chỉ đơn giản là lỗi nếu không có ai trong số họ có mặt. Tuy nhiên, tôi không quá bận tâm với nó bằng cách sử dụng 2.x gần đây nhất có thể, miễn là nó có thể tìm thấy một trong những thông dịch viên chính xác nếu có.

Xu hướng đầu tiên của tôi là thay đổi dòng shebang từ:

#!/usr/bin/python

đến

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

vì điều này hoạt động tốt trong bash. Nhưng chạy tập lệnh cho:

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

Được rồi, vì vậy tôi thử cách sau, cũng hoạt động trong bash:

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

Nhưng một lần nữa, điều này thất bại với:

/bin/bash: - : invalid option

Được rồi, rõ ràng tôi chỉ có thể viết một tập lệnh shell riêng biệt để tìm trình thông dịch chính xác và chạy tập lệnh python bằng cách sử dụng bất kỳ trình thông dịch nào nó tìm thấy. Tôi chỉ thấy rắc rối khi phân phối hai tệp trong đó một tệp sẽ đủ miễn là nó chạy với trình thông dịch python 2 cập nhật nhất được cài đặt. Yêu cầu mọi người gọi trình thông dịch một cách rõ ràng (ví dụ $ python2.5 script.py:) không phải là một lựa chọn. Dựa vào PATH của người dùng được thiết lập theo một cách nhất định cũng không phải là một lựa chọn.

Biên tập:

Kiểm tra phiên bản trong tập lệnh Python sẽ không hoạt động vì tôi đang sử dụng câu lệnh "with" tồn tại kể từ Python 2.6 (và có thể được sử dụng trong 2.5 với from __future__ import with_statement). Điều này khiến tập lệnh thất bại ngay lập tức với SyntaxError không thân thiện với người dùng và ngăn tôi không có cơ hội kiểm tra phiên bản trước và phát ra lỗi thích hợp.

Ví dụ: (thử điều này với trình thông dịch Python nhỏ hơn 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')

Không thực sự những gì bạn muốn, để bình luận. Nhưng bạn có thể sử dụng import sys; sys.version_info()để kiểm tra xem người dùng có phiên bản python cần thiết hay không.
Bernhard

2
@Bernhard Có, điều này đúng, nhưng sau đó thì đã quá muộn để làm bất cứ điều gì về nó. Đối với tình huống thứ ba tôi đã liệt kê ở trên, việc chạy trực tiếp tập lệnh (nghĩa là ./script.py) sẽ khiến python2.4 thực thi nó, điều này sẽ khiến mã của bạn phát hiện ra rằng đó là phiên bản sai (và có lẽ là thoát). Nhưng có một python2.5 hoàn toàn tốt có thể được sử dụng làm thông dịch viên thay thế!
108471

2
Sử dụng tập lệnh bao bọc để tìm hiểu xem có một con trăn phù hợp hay không và execnếu có thì in lỗi.
Kevin

1
Vì vậy, làm điều đầu tiên trong cùng một tập tin.
Kevin

9
@ user108471: Bạn đang giả sử dòng shebang được xử lý bằng bash. Nó không phải, đó là một cuộc gọi hệ thống ( execve). Các đối số là chuỗi ký tự, không có Globing, không regexps. Đó là nó. Ngay cả khi đối số thứ nhất là "/ bin / bash" và các tùy chọn thứ hai ("-c ..."), các tùy chọn đó không được phân tách bằng shell. Chúng không được xử lý cho bash thực thi, đó là lý do tại sao bạn nhận được những lỗi đó. Thêm vào đó, shebang chỉ hoạt động nếu nó bắt đầu. Vì vậy, bạn đã hết may mắn ở đây, tôi sợ (thiếu một đoạn script tìm một trình thông dịch python và cung cấp cho nó một tài liệu TẠI ĐÂY, nghe có vẻ như một mớ hỗn độn khủng khiếp).
goldilocks

Câu trả lời:


12

Tôi không phải là chuyên gia, nhưng tôi tin rằng bạn không nên chỉ định phiên bản python chính xác để sử dụng và để lựa chọn đó cho hệ thống / người dùng.

Ngoài ra, bạn nên sử dụng thay vì đường dẫn mã hóa cứng để python trong script:

#!/usr/bin/env python

hoặc là

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

Nó được đề xuất bởi tài liệu Python trong tất cả các phiên bản:

Một lựa chọn tốt thường là

#!/usr/bin/env python

tìm kiếm trình thông dịch Python trong toàn bộ PATH. Tuy nhiên, một số Unice có thể không có lệnh env, vì vậy bạn có thể cần mã hóa cứng / usr / bin / python làm đường dẫn trình thông dịch.

Trong các bản phân phối khác nhau, Python có thể được cài đặt ở những nơi khác nhau, vì vậy envsẽ tìm kiếm nó trong PATH. Nó nên có sẵn trong tất cả các bản phân phối Linux chính và từ những gì tôi thấy trong FreeBSD.

Tập lệnh nên được thực thi với phiên bản Python có trong PATH của bạn và được chọn bởi bản phân phối của bạn *.

Nếu tập lệnh của bạn tương thích với tất cả các phiên bản Python ngoại trừ 2.4, bạn chỉ nên kiểm tra bên trong nó nếu nó được chạy trong Python 2.4 và in một số thông tin và thoát.

Thêm để đọc

  • Tại đây bạn có thể tìm thấy các ví dụ ở những nơi Python có thể được cài đặt trong các hệ thống khác nhau.
  • Ở đây bạn có thể tìm thấy một số lợi thế và bất lợi cho việc sử dụng env.
  • Ở đây bạn có thể tìm thấy các ví dụ về thao tác PATH và các kết quả khác nhau.

Chú thích

* Trong Gentoo có công cụ gọi là eselect. Sử dụng nó, bạn có thể đặt các phiên bản mặc định của các ứng dụng khác nhau (bao gồm cả Python) làm mặc định:

$ 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
Tôi đánh giá cao những gì tôi muốn làm là chống lại những gì sẽ được coi là thực hành tốt. Những gì bạn đã đăng là hoàn toàn hợp lý, nhưng đồng thời nó không phải là những gì tôi đang yêu cầu. Tôi không muốn người dùng của mình phải chỉ rõ kịch bản của mình vào phiên bản Python thích hợp khi hoàn toàn có thể phát hiện phiên bản Python phù hợp trong mọi tình huống tôi quan tâm.
108471

1
Vui lòng xem cập nhật của tôi để biết lý do tại sao tôi không thể "chỉ kiểm tra bên trong nếu nó được chạy trong Python 2.4 và in một số thông tin và thoát."
108471

Bạn đúng rồi. Tôi vừa tìm thấy câu hỏi này trên SO và bây giờ tôi có thể thấy rằng không có tùy chọn nào để làm điều đó nếu bạn muốn chỉ có một tệp ...
pbm

9

Dựa trên một số ý tưởng từ một vài bình luận, tôi đã xoay sở để cùng nhau tạo ra một bản hack thực sự xấu xí dường như hoạt động. Tập lệnh trở thành tập lệnh bash bao bọc tập lệnh Python và chuyển nó đến trình thông dịch Python thông qua "tài liệu ở đây".

Lúc bắt đầu:

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

Mã Python ở đây. Sau đó, vào cuối:

# EOF

Khi người dùng chạy tập lệnh, phiên bản Python gần đây nhất giữa 2.5 và 2.7 được sử dụng để diễn giải phần còn lại của tập lệnh dưới dạng tài liệu ở đây.

Một lời giải thích về một số shenanigans:

Nội dung ba trích dẫn mà tôi đã thêm cũng cho phép nhập cùng tập lệnh này dưới dạng mô-đun Python (mà tôi sử dụng cho mục đích thử nghiệm). Khi được nhập bởi Python, mọi thứ giữa trích dẫn ba lần thứ nhất và thứ hai được hiểu là một chuỗi cấp mô-đun và trích dẫn ba lần thứ ba được nhận xét. Phần còn lại là Python bình thường.

Khi được chạy trực tiếp (dưới dạng tập lệnh bash bây giờ), hai dấu ngoặc đơn đầu tiên trở thành một chuỗi trống và trích dẫn đơn thứ ba tạo thành một chuỗi khác với trích dẫn đơn thứ tư, chỉ chứa dấu hai chấm. Chuỗi này được Bash giải thích là không có. Mọi thứ khác là cú pháp Bash để tạo các nhị phân Python trong / usr / bin, chọn cái cuối cùng và chạy exec, chuyển phần còn lại của tệp làm tài liệu ở đây. Tài liệu ở đây bắt đầu bằng một trích dẫn ba lần Python chỉ chứa dấu băm / pound / octothorpe. Phần còn lại của tập lệnh sau đó được hiểu là bình thường cho đến khi dòng đọc '# EOF' chấm dứt tài liệu ở đây.

Tôi cảm thấy như điều này là sai lầm, vì vậy tôi hy vọng ai đó có một giải pháp tốt hơn.


Có lẽ nó không quá khó chịu sau tất cả;) +1
goldilocks

Nhược điểm của việc này là nó sẽ làm rối màu cú pháp trên hầu hết các biên tập viên
Lie Ryan

@LieRyan Điều đó phụ thuộc. Kịch bản của tôi sử dụng phần mở rộng tên tệp .py, mà hầu hết các trình soạn thảo văn bản thích khi chọn sử dụng cú pháp để tô màu. Theo giả thuyết, nếu tôi đổi tên cái này mà không có phần mở rộng .py, tôi có thể sử dụng một mô hình để gợi ý cú pháp đúng (ít nhất là cho người dùng Vim) với một cái gì đó như : # ft=python.
108471

7

Dòng shebang chỉ có thể chỉ định một đường dẫn cố định đến một trình thông dịch. Có một #!/usr/bin/envmẹo để tìm kiếm thông dịch viên trong PATHđó nhưng đó là nó. Nếu bạn muốn tinh tế hơn, bạn sẽ cần phải viết một số mã trình bao bọc.

Giải pháp rõ ràng nhất là viết một kịch bản lệnh bao bọc. Gọi tập lệnh python foo.realvà tạo tập lệnh bao bọc foo:

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

Nếu bạn muốn đặt mọi thứ vào một tệp, bạn thường có thể biến nó thành một polyglot bắt đầu bằng một #!/bin/shdòng (do đó sẽ được thực thi bởi trình bao) nhưng cũng là một tập lệnh hợp lệ trong ngôn ngữ khác. Tùy thuộc vào ngôn ngữ, #!ví dụ , một polyglot có thể là không thể (nếu gây ra lỗi cú pháp). Trong Python, nó không quá khó.

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

(Toàn bộ văn bản giữa ''''''là một chuỗi Python tại toplevel, không có tác dụng. Đối với trình bao, dòng thứ hai là ''':'sau khi tước dấu ngoặc kép là lệnh no-op :.)


Giải pháp thứ hai là tốt vì nó không yêu cầu thêm # EOFvào cuối như trong câu trả lời này . Cách tiếp cận của bạn về cơ bản là giống như được phác thảo ở đây .
sschuberth

6

Vì yêu cầu của bạn nêu ra một danh sách nhị phân đã biết, bạn có thể thực hiện nó trong Python với các mục sau. Nó sẽ không hoạt động qua một phiên bản nhỏ / chính của Python nhưng tôi không thấy điều đó xảy ra sớm.

Chạy phiên bản cao nhất nằm trên đĩa từ danh sách các pythons được phiên bản, tăng lên, nếu phiên bản được gắn thẻ trên nhị phân cao hơn phiên bản thực thi python hiện tại. "Danh sách các phiên bản tăng theo thứ tự" là bit quan trọng cho mã này.

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

Xin lỗi cho con trăn cào của tôi


nó sẽ không tiếp tục chạy sau khi thực hiện chứ? những thứ bình thường sẽ được thực hiện hai lần?
Janus Troelsen

1
execv thay thế chương trình hiện đang thực thi bằng hình ảnh chương trình mới được tải
Matt

Đây có vẻ là một giải pháp tuyệt vời mà cảm thấy ít xấu xí hơn những gì tôi nghĩ ra. Tôi sẽ phải thử cái này xem nó có hoạt động cho mục đích này không.
108471

Đề nghị này gần như hoạt động cho những gì tôi cần. Lỗ hổng duy nhất là điều mà ban đầu tôi không đề cập: để hỗ trợ Python 2.5, tôi sử dụng from __future__ import with_statement, đây phải là điều đầu tiên trong tập lệnh Python. Tôi không cho rằng bạn tình cờ biết cách thực hiện hành động đó khi bắt đầu trình thông dịch mới?
108471

bạn có chắc nó cần phải được sự rất Điều đầu tiên? hoặc ngay trước khi bạn thử và sử dụng bất kỳ withs như nhập khẩu bình thường?. Có phải một if mypy == 25: from __future__ import with_statementcông việc làm thêm ngay trước khi 'công cụ bình thường'? Bạn có thể không cần nếu, nếu bạn không hỗ trợ 2.4.
Matt

0

Bạn có thể viết một tập lệnh bash nhỏ để kiểm tra thực thi phython có sẵn và gọi nó với tập lệnh là tham số. Sau đó, bạn có thể đặt tập lệnh này thành mục tiêu dòng shebang:

#!/my/python/search/script

Và kịch bản này chỉ đơn giản là làm (sau khi tìm kiếm):

"$python_path" "$1"

Tôi không chắc liệu kernel có chấp nhận tập lệnh này hay không nhưng tôi đã kiểm tra và nó hoạt động.

Chỉnh sửa 1

Để làm cho sự không chấp nhận lúng túng này trở thành một đề xuất tốt cuối cùng:

Có thể kết hợp cả hai tập lệnh trong một tập tin. Bạn chỉ cần viết tập lệnh python như một tài liệu ở đây trong tập lệnh bash (nếu bạn thay đổi tập lệnh python, bạn chỉ cần sao chép các tập lệnh lại với nhau). Hoặc bạn tạo một tệp tạm thời trong eg / tmp hoặc (nếu python hỗ trợ điều đó, tôi không biết) bạn cung cấp tập lệnh làm đầu vào cho trình thông dịch:

# 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

Đây ít nhiều là giải pháp đã được nêu trong đoạn cuối của qe bỏng!
Bernhard

Thông minh, nhưng nó đòi hỏi kịch bản ma thuật này phải được cài đặt ở đâu đó trên mọi hệ thống.
108471

@Bernhard Oooops, bị bắt. Trong tương lai tôi sẽ đọc đến cuối. Khi bồi thường, tôi sẽ cải thiện nó thành một giải pháp một tập tin.
Hauke ​​Laging

@ user108471 Kịch bản ma thuật có thể chứa một cái gì đó như thế này: $(ls /usr/bin/python?.? | tail -n1 )nhưng tôi đã không thành công khi sử dụng nó một cách khéo léo trong một shebang.
Bernhard

@Bernhard Bạn muốn thực hiện tìm kiếm trong dòng shebang? IIRC kernel không quan tâm đến việc trích dẫn trong dòng shebang. Mặt khác (nếu điều này đã thay đổi trong khi đó) thì người ta có thể làm một cái gì đó như `#! / Bin / bash -c do_search_here_without_whitespace ...; exec $ python" $ 1 "Nhưng làm thế nào mà không có khoảng trắng?
Hauke ​​Laging
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.