เลือกล่ามหลังจากเริ่มต้นสคริปต์เช่นถ้า / else อยู่ใน hashbang


16

มีวิธีใดบ้างในการเลือกล่ามที่กำลังเรียกใช้สคริปต์แบบไดนามิก ฉันมีสคริปต์ที่ฉันใช้ในระบบที่แตกต่างกันสองระบบและล่ามที่ฉันต้องการใช้นั้นจะอยู่ในตำแหน่งที่ตั้งที่ต่างกันในทั้งสองระบบ สิ่งที่ฉันต้องทำคือเปลี่ยนสาย hashbang ทุกครั้งที่เปลี่ยนไป ฉันต้องการทำบางสิ่งที่เทียบเท่ากับตรรกะของสิ่งนี้ (ฉันรู้ว่าการสร้างที่แน่นอนนี้เป็นไปไม่ได้):

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 และการใช้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 (หรือบางระบบ templating ง่าย) เพื่อค้นหาล่ามและวางเส้นทางที่ถูกต้องลงในไฟล์ใน./configureขั้นตอนซึ่งจะทำงานเมื่อติดตั้งสคริปต์ในแต่ละระบบ

เราอาจใช้วิธีการเรียกใช้สคริปต์ด้วยล่ามที่ชัดเจน แต่นั่นก็เป็นสิ่งที่คุณพยายามหลีกเลี่ยง:

$ sed -f script.sed

ใช่ฉันรู้ว่า#!ต้องมาตั้งแต่แรกเพราะมันไม่ใช่เชลล์ที่ประมวลผลบรรทัดนั้น ฉันสงสัยว่ามีวิธีใส่ตรรกะในบรรทัด hashbang ที่จะเทียบเท่ากับ if / else หรือไม่ ฉันยังหวังที่จะหลีกเลี่ยงการยุ่งกับฉันPATHแต่ฉันคิดว่ามันเป็นตัวเลือกเดียวของฉัน
dkv

1
เมื่อคุณใช้คุณอาจให้ตรงหนึ่งอาร์กิวเมนต์เป็น#!/usr/bin/awk #!/usr/bin/awk -fหากไบนารีที่คุณกำลังจะชี้เป็นenvอาร์กิวเมนต์เป็นไบนารีที่คุณขอที่จะมองหาในขณะที่env #!/usr/bin/env awk
DopeGhoti

2
@dkv มันไม่ได้ มันใช้ล่ามที่มีสองข้อโต้แย้งและมันอาจทำงานได้ในบางระบบ แต่ไม่แน่นอนในทุกระบบ
Kusalananda

3
@dkv บน Linux มันจะทำงานกับอาร์กิวเมนต์เดียว/usr/bin/env awk -f
ilkkachu

1
@ Kusalananda ไม่นั่นเป็นประเด็น หากคุณมีสคริปต์ที่เรียกว่าfoo.awkมีเส้น hashbang #!/usr/bin/env awk -fและเรียกมันด้วย./foo.awkแล้วบน Linux, สิ่งที่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ใน hashbang เพราะflagsอาร์เรย์ #!/bin/shถ้าคุณไม่จำเป็นต้องเก็บจำนวนตัวแปรของธงหรือดังกล่าวและสามารถทำโดยไม่สคริปต์ควรจะทำงานกับ portably


2
ฉันเคยเห็นexec "$interpreter" "${flags[@]}" "$script" "$@"ยังใช้ในการรักษาต้นไม้กระบวนการ นอกจากนี้ยังเผยแพร่รหัสออก
ruuenza

@rrauenza execอาใช่ธรรมชาติ
ilkkachu

1
จะ #!/bin/shดีกว่า#!/bin/bashไหม แม้ว่า/bin/shการเชื่อมโยงไปยังเชลล์ที่แตกต่างกันมันควรมีอยู่ในระบบ * ส่วนใหญ่ (ถ้าไม่ใช่ทั้งหมด) และมันจะบังคับให้ผู้เขียนสคริปต์สร้างสคริปต์แบบพกพาแทนที่จะตกอยู่ใน bashisms
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy, เฮ้ฉันคิดเกี่ยวกับการพูดถึงก่อนหน้านี้ แต่ไม่ได้แล้ว อาร์เรย์ที่ใช้flagsเป็นคุณสมบัติที่ไม่ได้มาตรฐาน แต่ก็มีประโยชน์เพียงพอสำหรับการจัดเก็บค่าสถานะจำนวนตัวแปรดังนั้นฉันจึงตัดสินใจเก็บไว้
ilkkachu

หรือการใช้ / bin / 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เพื่อป้องกันเชลล์ไม่ให้รันมากกว่าส่วนแรก

ตัวอย่าง Python:

#!/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 ละเว้นทุกอย่างขึ้นอยู่กับการจับคู่!#; สิ่งนี้ช่วยให้คุณสามารถวางโค้ดสคริปต์ที่ซับซ้อนตามอำเภอใจในภาษาที่กำหนดไว้แล้วจากนั้นexecScala execution engine พร้อมกับสคริปต์
Jörg W Mittag

1
@ Jörg W Mittag: +1 สำหรับ Scala
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และทุกสิ่งหลังจากที่มันถูกนำมาต่อรองเป็นทับทิม 1 อาร์กิวเมนต์ เท่าที่ฉันสามารถบอกได้คุณไม่สามารถใช้ bash สำหรับโค้ด shebang ได้เนื่องจากbash -cต้องการให้รหัสนั้นอยู่ในอาร์กิวเมนต์ที่แยกต่างหาก

ฉันพยายามทำเช่นเดียวกันกับ python สำหรับโค้ด 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

แต่มันกลับกลายมานานเกินไปและ linux (อย่างน้อยในเครื่องของฉัน) ตัด shebang ให้เหลือ 127 ตัวอักษร โปรดแก้ตัวการใช้execการแทรก newlines เนื่องจาก python ไม่อนุญาตให้ทดลองใช้ยกเว้นหรือimports โดยไม่ขึ้นบรรทัดใหม่

ฉันไม่แน่ใจว่าอุปกรณ์พกพานี้เป็นอย่างไรและฉันจะไม่ทำตามรหัสที่จะเผยแพร่ อย่างไรก็ตามมันเป็นไปได้ อาจมีบางคนพบว่ามีประโยชน์สำหรับการดีบักอย่างรวดเร็วและสกปรกหรือบางอย่าง


2

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

สร้าง symlink (หรือ hardlink ถ้าต้องการ) เพื่อชี้ไปที่เส้นทางล่ามที่ต้องการ ตัวอย่างเช่นในระบบ perl และ python ของฉันอยู่ใน / usr / bin:

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

จะสร้าง symlink เพื่ออนุญาตให้ hashbang แก้ไขสำหรับ / bin / perl เป็นต้นซึ่งจะช่วยรักษาความสามารถในการส่งผ่านพารามิเตอร์ไปยังสคริปต์ได้เช่นกัน


1
+1 มันง่ายมาก อย่างที่คุณทราบมันไม่ได้ตอบคำถาม แต่ดูเหมือนว่าจะทำในสิ่งที่ OP ต้องการ แม้ว่าฉันเดาว่าการใช้ env จะช่วยให้สามารถเข้าถึงรูทของแต่ละเครื่องได้
Joe

0

ฉันกำลังเผชิญหน้ากับปัญหาที่คล้ายกันเช่นนี้ในวันนี้ ( python3ชี้ไปที่รุ่นของไพ ธ อนที่เก่าเกินไปในระบบเดียว) และเกิดวิธีการที่แตกต่างจากที่กล่าวไว้เล็กน้อย: ใช้เวอร์ชัน "ผิด" ของ หลามเพื่อ bootstrap เป็น "ขวา" หนึ่ง ข้อ จำกัด คือว่าบางรุ่นของงูหลามจะต้องสามารถเข้าถึงได้อย่างน่าเชื่อถือ #!/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.