스크립트 시작 후 해석기 선택 (예 : hashbang 내부의 if / else)


16

스크립트를 실행하는 인터프리터를 동적으로 선택할 수있는 방법이 있습니까? 두 개의 다른 시스템에서 실행중인 스크립트가 있는데 사용하려는 인터프리터가 두 시스템의 다른 위치에 있습니다. 내가 끝내야 할 것은 전환 할 때마다 해시 뱅 라인을 변경하는 것입니다. 나는 이것과 논리적으로 동등한 것을하고 싶습니다 (이 정확한 구조는 불가능하다는 것을 알고 있습니다).

if running on system A:
    #!/path/to/python/on/systemA
elif running on system B:
    #!/path/on/systemB

#Rest of script goes here

또는 이것이 더 좋을 것이므로 첫 번째 인터프리터를 사용하려고 시도하고 찾지 못하면 두 번째 인터프리터를 사용하십시오.

try:
    #!/path/to/python/on/systemA
except: 
    #!path/on/systemB

#Rest of script goes here

분명히, 대신에 /path/to/python/on/systemA myscript.py 또는 현재 위치 /path/on/systemB myscript.py 에 따라 실행할 수 있지만 실제로 시작하는 래퍼 스크립트가 myscript.py있으므로 직접 파이썬 인터프리터의 경로를 프로그래밍 방식으로 지정하고 싶습니다.


3
shebang없이 'rest of script'를 파일로 인터프리터에 전달하고 if조건을 사용하는 것은 당신에게 옵션이 아닙니다. 같은if something; then /bin/sh restofscript.sh elif...
mazs

그것은 옵션이지만, 나는 그것을 고려했지만 원하는 것보다 다소 지저분합니다. hashbang 라인의 논리는 불가능하기 때문에 실제로 그 길을 갈 것이라고 생각합니다.
dkv

이 질문에 의해 생성 된 다양한 답변이 마음에 듭니다.
Oskar Skog

답변:


27

아니요, 작동하지 않습니다. 두 문자 #!는 파일에서 처음 두 문자 여야합니다. 어쨌든 if 문을 해석하는 것을 어떻게 지정 하시겠습니까? 이것은 exec()실행하려는 파일이 스크립트 (인터프리터가 필요함)인지 바이너리 파일 (필수 아님)인지를 판단 할 때 함수 계열이 감지 하는 "마법 번호"를 구성합니다 .

shebang 라인의 형식은 매우 엄격합니다. 인터프리터에 대한 절대 경로와 그에 대한 최대 하나의 인수가 필요합니다.

당신이 할 수있는 일은 사용하는 것입니다 env:

#!/usr/bin/env interpreter

일반적으로 경로 env일반적으로 /usr/bin/env 기술적으로 보장되지 않습니다.

이것은 당신이 조정할 수 있습니다 PATH즉 각각의 시스템에 환경 변수를 interpreter(이라고해도 bash, python또는 perl또는 무엇이든 당신이) 발견된다.

이 접근법의 단점은 인터프리터에게 인수를 전달할 수 없다는 것입니다.

이것은

#!/usr/bin/env awk -f

#!/usr/bin/env sed -f

일부 시스템에서는 작동하지 않을 수 있습니다.

또 다른 확실한 접근 방법은 GNU autotools (또는 일부 간단한 템플릿 시스템)를 사용하여 인터프리터를 찾고 ./configure각 시스템에 스크립트를 설치할 때 실행되는 단계 에서 파일에 올바른 경로를 배치 하는 것입니다.

명시 적 인터프리터를 사용하여 스크립트를 실행하는 것도 가능하지만 피하려고하는 것은 분명합니다.

$ sed -f script.sed

그렇습니다. 나는 #!그 라인을 처리하는 쉘이 아니기 때문에 처음에 와야 한다는 것을 알고 있습니다. 논리를 넣을 수있는 방법이 있는지 궁금 해서요 내부 는 IF / 다른 사람에 상응하는 것 hashbang 라인. 나는 또한 내 주위를 어지럽히 지 않기를 바랐 PATH지만 이것이 유일한 옵션이라고 생각합니다.
dkv

1
를 사용할 때 #!/usr/bin/awk와 같이 정확히 하나의 인수를 제공 할 수 있습니다 #!/usr/bin/awk -f. 가리키는 바이너리가 env인 경우 인수는에서와 같이 찾고자하는 바이너리 env입니다 #!/usr/bin/env awk.
DopeGhoti

2
@dkv 아닙니다. 두 개의 인수로 된 인터프리터를 사용하며 일부 시스템에서는 작동하지만 완전히 작동하지는 않습니다.
Kusalananda

3
Linux에서 @dkv /usr/bin/env는 단일 인수로 실행됩니다 awk -f.
ilkkachu

1
@ Kusalananda는 아닙니다. 당신이라는 스크립트가있는 경우 foo.awkhashbang 라인을 #!/usr/bin/env awk -f과로 전화 ./foo.awk후, 리눅스, 무엇을 env보고하는 것은 두 개의 매개 변수입니다 awk -f./foo.awk. 실제로 /usr/bin/awk -f공백이있는 등을 찾습니다 .
ilkkachu

27

실제 프로그램에 맞는 올바른 인터프리터를 찾기 위해 래퍼 스크립트를 만들 수 있습니다.

#!/bin/bash
if something ; then
    interpreter=this
    script=/some/path/to/program.real
    flags=()
else
    interpreter=that
    script=/other/path/to/program.real
    flags=(-x -y)
fi
exec "$interpreter" "${flags[@]}" "$script" "$@"

랩퍼를 사용자의 이름 PATH으로 저장하고 program실제 프로그램을 따로 또는 다른 이름으로 두십시오.

배열 #!/bin/bash때문에 해시 뱅에서 사용 했습니다 flags. 가변 개수의 플래그 등을 저장할 필요가없고 플래그없이 수행 할 수있는 경우 스크립트가와 함께 작동해야합니다 #!/bin/sh.


2
exec "$interpreter" "${flags[@]}" "$script" "$@"프로세스 트리를 더 깨끗하게 유지하는 데 사용되는 것을 보았습니다 . 종료 코드도 전파됩니다.
rrauenza

@rrauenza, 아 그렇습니다 exec.
ilkkachu

1
#!/bin/sh대신에 더 좋지 않을까요 #!/bin/bash? /bin/sh다른 쉘에 대한 심볼릭 링크 인 경우에도 대부분의 * nix 시스템에 존재해야하며 스크립트 작성자는 bashism에 속하지 않고 이식 가능한 스크립트를 작성해야합니다.
Sergiy Kolodyazhnyy

@ SergiyKolodyazhnyy, heh, 나는 그것을 언급하는 것에 대해 생각했지만, 그렇지 않았습니다. 사용 된 배열 flags은 비표준 기능이지만 가변 수의 플래그를 저장하는 데 유용하므로 보관하기로 결정했습니다.
ilkkachu

또는 / bin / sh를 사용하고 각 분기에서 직접 인터프리터를 호출하십시오 script=/what/ever; something && exec this "$script" "$@"; exec that "$script" -x -y "$@". exec 실패에 대한 오류 검사를 추가 할 수도 있습니다.
jrw32982는 Monica

11

폴리 글롯을 작성할 수도 있습니다 (두 언어 결합). / bin / sh가 존재합니다.

이것은 추악한 코드의 단점이 있으며 아마도 일부 /bin/sh는 잠재적으로 혼란 스러울 수 있습니다. 그러나 env존재하지 않거나 / usr / bin / env 이외의 곳에 존재할 때 사용할 수 있습니다 . 꽤 멋진 선택을 원한다면 사용할 수도 있습니다.

스크립트의 첫 번째 부분은 / bin / sh를 인터프리터로 실행할 때 사용할 인터프리터를 결정하지만 올바른 인터프리터에 의해 실행될 때는 무시됩니다. exec쉘이 첫 번째 부분보다 많이 실행되지 않도록하는 데 사용하십시오 .

파이썬 예제 :

#!/bin/sh
'''
' 2>/dev/null
# Python thinks this is a string, docstring unfortunately.
# The shell has just tried running the <newline> program.
find_best_python ()
{
    for candidate in pypy3 pypy python3 python; do
        if [ -n "$(which $candidate)" ]; then
            echo $candidate
            return
        fi
    done
    echo "Can't find any Python" >/dev/stderr
    exit 1
}
interpreter="$(find_best_python)"   # Replace with something fancier.
# Run the rest of the script
exec "$interpreter" "$0" "$@"
'''

3
나는 이것들 중 하나를 본 적이 있다고 생각하지만, 그 아이디어는 여전히 똑같습니다 ... 그러나 아마도 exec "$interpreter" "$0" "$@"스크립트 자체의 이름을 실제 통역사에게도 원할 것입니다 . (그리고 설정할 때 아무도 거짓말을하지 않기를 바랍니다 $0.)
ilkkachu

6
Scala는 실제로 구문에서 폴리 글롯 스크립트를 지원합니다. Scala 스크립트가로 시작 #!하면 Scala는 일치하는 모든 것을 무시합니다 !#. 이를 통해 임의로 복잡한 스크립트 코드를 임의의 언어로 넣은 다음 execScala 실행 엔진을 스크립트와 함께 사용할 수 있습니다.
Jörg W Mittag

1
@ Jörg W Mittag : Scala의 경우 +1
jrw32982는 Monica를 지원합니다.

2

나는 Kusalananda와 ilkkachu의 대답을 선호하지만 여기에 단순히 질문했기 때문에 질문이 요구하는 것을보다 직접적으로 수행하는 대체 대답이 있습니다.

#!/usr/bin/ruby -e exec "non-existing-interpreter", ARGV[0] rescue exec "python", ARGV[0]

if True:
  print("hello world!")

인터프리터가 첫 번째 인수에서 코드 작성을 허용 한 경우에만이 작업을 수행 할 수 있습니다. 여기, -e그리고 그 이후의 모든 것은 루비에 대한 하나의 인수로 그대로 사용됩니다. 내가 알 bash -c수있는 한, 코드가 별도의 인수에 있어야 하기 때문에 shebang 코드에 bash를 사용할 수 없습니다 .

shebang 코드에 대해 파이썬으로 동일한 작업을 시도했습니다.

#!/usr/bin/python -cexec("import sys,os\ntry: os.execlp('non-existing-interpreter', 'non-existing-interpreter', sys.argv[1])\nexcept: os.execlp('ruby', 'ruby', sys.argv[1])")

if true
  puts "hello world!"
end

그러나 너무 길고 리눅스 (적어도 내 컴퓨터에서는)가 shebang을 127 자로 자릅니다. exec파이썬은 import개행없이 시도 예외 또는 s를 허용하지 않으므로 개행을 삽입 하는 사용을 용서하십시오 .

이것이 얼마나 이식성이 있는지 잘 모르겠으며 배포하려는 코드에서는 그렇게하지 않을 것입니다. 그럼에도 불구하고 가능합니다. 어쩌면 누군가가 빠르고 더러운 디버깅이나 무언가에 유용하다는 것을 알게 될 것입니다.


2

이것은 셸 스크립트 내에서 인터프리터를 선택하지 않지만 (컴퓨터 당 선택) 스크립트를 실행하려는 모든 컴퓨터에 대한 관리 액세스 권한이있는 경우 더 쉬운 대안입니다.

원하는 인터프리터 경로를 가리 키도록 심볼릭 링크 (또는 원하는 경우 하드 링크)를 만듭니다. 예를 들어, 내 시스템에서 perl과 python은 / usr / bin에 있습니다.

cd /bin
ln -s /usr/bin/perl perl
ln -s /usr/bin/python python

해시 뱅이 / bin / perl 등을 해결할 수 있도록 심볼릭 링크를 생성합니다. 이렇게하면 매개 변수를 스크립트에 전달할 수 있습니다.


1
+1 이것은 매우 간단합니다. 아시다시피, 그것은 질문에 대한 대답은 아니지만 OP가 원하는 것을 정확하게하는 것처럼 보입니다. env를 사용하면 각 컴퓨터 문제에 대한 루트 액세스 권한을 얻습니다.
Joe

0

나는 오늘 이와 비슷한 문제에 직면했다 ( python3하나의 시스템에서 너무 오래된 파이썬 버전을 가리키고있다). 파이썬은 "올바른"부트 스트랩. 제한 사항은 일부 버전의 Python에 안정적으로 도달 할 수 있어야하지만 일반적으로 예를 들어 #!/usr/bin/env python3.

그래서 내가하는 일은 다음과 같이 스크립트를 시작하는 것입니다.

#!/usr/bin/env python3
import sys
import os

# On one of our systems, python3 is pointing to python3.3
# which is too old for our purposes. 'Upgrade' if needed
if sys.version_info[1] < 4:
    for py_version in ['python3.7', 'python3.6', 'python3.5', 'python3.4']:
        try:
            os.execlp(py_version, py_version, *sys.argv)
        except:
            pass # Deliberately ignore errors, pick first available version

이것이하는 일은 :

  • 수락 기준에 대해서는 인터프리터 버전을 확인하십시오.
  • 허용되지 않는 경우 후보 버전 목록을 살펴보고 사용 가능한 첫 번째 버전으로 다시 실행하십시오.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.