스크립트의 쉐방 라인에서 어떻게 하나 이상의 가능성을 가질 수 있습니까?


16

나는 다양한 환경 (및 PATH)과 다양한 Linux 시스템을 가진 다양한 사용자가 이론적으로 실행할 수있는 Python 스크립트가있는 흥미로운 상황에 처해 있습니다. 인공적인 제한없이이 스크립트를 가능한 많은 스크립트에서 실행할 수 있기를 바랍니다. 알려진 설정은 다음과 같습니다.

  • 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]

이것은 bash에서 잘 작동하기 때문입니다. 그러나 스크립트를 실행하면 다음이 제공됩니다.

/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 2.6에서 존재하는 "with"문을 사용하고 있기 때문에 Python 스크립트 내에서 버전 확인 기능 이 작동 하지 않습니다 (2.5에서와 함께 사용할 수 있음 from __future__ import with_statement). 이로 인해 스크립트가 사용자에게 친숙하지 않은 SyntaxError와 함께 즉시 실패하고 버전을 먼저 확인하여 적절한 오류가 발생하는 것을 막을 수 있습니다.

예 : (2.6 미만의 Python 인터프리터로 시도하십시오)

#!/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
@Bernhard 네, 맞습니다.하지만 그때까지는 아무 일도하지 않았습니다. 위에 나열된 세 번째 상황에서 스크립트를 직접 실행하면 (예 ./script.py:) python2.4가 스크립트 를 실행하여 코드가 잘못된 버전임을 감지하고 종료 될 수 있습니다. 그러나 대신 통역사로 사용될 수있는 완벽하게 좋은 python2.5가 있습니다!
user108471

2
랩퍼 스크립트를 사용하여 적절한 파이썬 exec이 있는지 확인하고, 그렇지 않은 경우 오류를 인쇄하십시오.
케빈

1
따라서 동일한 파일에서 먼저 수행하십시오.
케빈

9
@ user108471 : shebang 행이 bash에 의해 처리된다고 가정합니다. 시스템 호출이 아닙니다 ( execve). 인수는 문자열 리터럴, globbing 없음, 정규 표현식 없음입니다. 그게 다야. 첫 번째 인수가 "/ bin / bash"이고 두 번째 옵션 ( "-c ...") 인 경우에도 해당 옵션은 쉘에 의해 구문 분석되지 않습니다. 그것들은 bash 실행 파일로 처리되지 않으므로 이러한 오류가 발생합니다. 또한 shebang은 처음 시작하는 경우에만 작동합니다. 그래서 당신은 운이 좋지 않습니다. 두려워합니다 (파이썬 인터프리터를 찾아서 여기에 닥터에게 먹이는 스크립트가 부족합니다).
goldilocks

답변:


13

나는 전문가가 아니지만 정확한 파이썬 버전을 지정하여 사용하지 말고 그 선택을 시스템 / 사용자에게 맡겨서는 안된다고 생각합니다.

또한 스크립트에서 파이썬의 하드 코딩 경로 대신 이것을 사용해야합니다.

#!/usr/bin/env python

또는

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

모든 버전에서 Python doc권장 합니다 .

좋은 선택은 일반적으로

#!/usr/bin/env python

전체 PATH에서 Python 인터프리터를 검색합니다. 그러나 일부 Unices에는 env 명령이 없을 수 있으므로 / usr / bin / python을 인터프리터 경로로 하드 코딩해야 할 수도 있습니다.

다양한 배포판에서 Python은 다른 위치에 설치 될 수 있으므로에서 env검색하십시오 PATH. 모든 주요 Linux 배포판과 FreeBSD에서 볼 수있는 것이어야합니다.

스크립트는 PATH에 있으며 배포판에서 선택한 Python 버전으로 실행해야합니다 *.

스크립트가 2.4를 제외한 모든 Python 버전과 호환되는 경우 Python 2.4에서 실행되는지 확인하고 일부 정보를 인쇄하고 종료해야합니다.

더 읽을 거리

  • 여기서 다른 시스템에 Python을 설치할 수있는 위치에 대한 예를 찾을 수 있습니다.
  • 여기서 사용에 대한 몇 가지 장단점을 찾을 수 있습니다 env.
  • 여기서 PATH 조작 및 다른 결과의 예를 찾을 수 있습니다.

각주

* 젠투에는 도구라는 도구가 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

몇 가지 의견에서 얻은 아이디어를 바탕으로, 나는 작동하는 것처럼 보이는 추악한 해킹을 함께 모을 수있었습니다. 이 스크립트는 bash 스크립트가되어 Python 스크립트를 랩핑하여 "here document"를 통해 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

사용자가 스크립트를 실행할 때 2.5에서 2.7 사이의 최신 Python 버전이 나머지 문서를 여기 문서로 해석하는 데 사용됩니다.

일부 shenanigans에 대한 설명 :

내가 추가 한 삼중 인용 항목도 동일한 스크립트를 Python 모듈 (테스트 목적으로 사용)로 가져올 수 있습니다. 파이썬에 의해 임포트 될 때, 첫 번째와 두 개의 삼중 인용 부호 사이의 모든 것은 모듈 레벨 문자열로 해석되고 세 번째 삼중 인용 부호는 주석 처리됩니다. 나머지는 일반적인 파이썬입니다.

bash 스크립트로 직접 실행하면 처음 두 개의 작은 따옴표가 빈 문자열이되고 세 번째 작은 따옴표는 콜론 만 포함하는 네 번째 작은 따옴표로 다른 문자열을 형성합니다. 이 문자열은 Bash에 의해 no-op로 해석됩니다. 다른 모든 것은 / usr / bin에서 Python 바이너리를 가져 와서 마지막 바이너리를 선택하고 exec를 실행하여 나머지 파일을 here 문서로 전달하는 Bash 구문입니다. 이 문서는 해시 / 파운드 / 옥토 프 기호 만 포함 된 Python 삼중 따옴표로 시작합니다. 그런 다음 나머지 스크립트는 '# EOF'를 읽는 행이 여기 문서를 종료 할 때까지 정상으로 해석됩니다.

나는 이것이 이상하다고 생각하므로 누군가 더 나은 해결책을 갖기를 바랍니다.


어쩌면 그것은 그렇게 불쾌하지 않습니다;) +1
goldilocks

단점은 대부분의 편집기에서 구문 색상이 엉망이된다는 것입니다.
Lie Ryan

@LieRyan 그것은 달려 있습니다. 내 스크립트는 .py 파일 이름 확장자를 사용합니다. 대부분의 텍스트 편집기는 색상 지정에 사용할 구문을 선택할 때 선호합니다. 가설 적으로 .py 확장자없이 이름을 바꾸려면 모델 론을 사용하여 (최소한 Vim 사용자에게) 다음과 같은 올바른 구문을 암시 할 수 있습니다 # ft=python.
user108471

7

shebang 행은 인터프리터에 대한 고정 경로 만 지정할 수 있습니다. 거기의 #!/usr/bin/env에서 통역을 찾기 위해 트릭 PATH하지만 그것 뿐이다. 더 정교하고 싶다면 래퍼 쉘 코드를 작성해야합니다.

가장 확실한 해결책은 랩퍼 스크립트를 작성하는 것입니다. 파이썬 스크립트를 호출하고 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있지만 다른 언어에서는 유효한 스크립트이기도합니다. 언어에 따라 폴리 글롯이 불가능할 수 있습니다 ( #!예 : 구문 오류가 발생하는 경우 ). 파이썬에서는 그리 어렵지 않습니다.

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

( '''와 사이의 전체 텍스트 '''는 최상위 수준의 파이썬 문자열이며 효과가 없습니다. 셸의 경우 두 번째 줄은 ''':'따옴표를 제거한 후 no-op 명령 :입니다.)


두 번째 솔루션은 이 답변# EOF 과 같이 끝에 추가 할 필요가 없기 때문에 좋습니다. 귀하의 접근 방식은 기본적으로 여기에 설명 된 것과 같습니다 .
sschuberth

6

요구 사항에 알려진 바이너리 목록이 명시되어 있으므로 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를 지원하지 않으면 if가 필요하지 않을 것입니다.
Matt

0

사용 가능한 phython 실행 파일을 확인하고 스크립트를 매개 변수로 사용하여 작은 bash 스크립트를 작성할 수 있습니다. 그런 다음이 스크립트를 shebang 라인 대상으로 만들 수 있습니다.

#!/my/python/search/script

그리고이 스크립트는 단순히 (검색 후) 수행합니다.

"$python_path" "$1"

커널이이 스크립트를 간접적으로 받아 들일지 확신 할 수 없었지만 확인하고 작동합니다.

편집 1

이 난처한 인식을 좋은 제안으로 만들려면 :

두 스크립트를 하나의 파일로 결합 할 수 있습니다. bash 스크립트에서 python 스크립트를 here 문서로 작성하기 만하면됩니다 (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 2013

@ user108471 매직 스크립트에는 다음과 같은 내용이 포함될 수 있습니다 $(ls /usr/bin/python?.? | tail -n1 ).
Bernhard

@Bernhard shebang 라인 내에서 검색을 원하십니까? IIRC 커널은 shebang 라인에서 인용하는 것을 신경 쓰지 않습니다. 그렇지 않으면 (이것이 그 사이에 바뀌었다면)`#! / bin / bash -c do_search_here_without_whitespace ...; exec $ python "$ 1"과 같은 것을 할 수 있습니다. 그러나 공백없이 어떻게합니까?
Hauke ​​Laging 2013
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.