มีการโต้แย้งหลายครั้งใน shebang


32

ฉันสงสัยว่ามีวิธีทั่วไปในการส่งตัวเลือกหลายตัวไปยังโปรแกรมที่เรียกใช้งานได้ผ่านทางสาย shebang ( #!)

ผมใช้ NixOS และส่วนแรกของ shebang ในสคริปต์ใด ๆ /usr/bin/envที่ฉันเขียนมักจะ ปัญหาที่ฉันพบคือทุกสิ่งที่เกิดขึ้นหลังจากนั้นถูกตีความว่าเป็นไฟล์หรือไดเรกทอรีเดียวโดยระบบ

ตัวอย่างเช่นสมมติว่าฉันต้องการเขียนสคริปต์ที่จะดำเนินการโดยbashในโหมด posix วิธีที่ไร้เดียงสาของการเขียน Shebang จะเป็น:

#!/usr/bin/env bash --posix

แต่พยายามที่จะรันสคริปต์ผลลัพธ์จะสร้างข้อผิดพลาดต่อไปนี้:

/usr/bin/env: ‘bash --posix’: No such file or directory

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


แก้ไข : ฉันรู้ว่าสำหรับสคริปต์Guileมีวิธีการบรรลุสิ่งที่ฉันต้องการบันทึกไว้ในส่วน 4.3.4ของคู่มือ:

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

เคล็ดลับที่นี่ก็คือบรรทัดที่สอง (เริ่มต้นด้วยexec) ถูกตีความว่าเป็นรหัสโดยshแต่อยู่ใน#!... !#บล็อกเป็นความคิดเห็นและทำให้ละเลยโดยล่าม Guile

จะไม่สามารถสรุปวิธีการนี้กับล่ามใด ๆ ได้หรือไม่?


แก้ไขที่สอง : หลังจากเล่นไปนิด ๆ หน่อย ๆ ดูเหมือนว่าสำหรับล่ามที่สามารถอ่านอินพุตของพวกเขาได้stdinวิธีต่อไปนี้จะใช้ได้:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

อาจเป็นวิธีที่ไม่เหมาะสมแม้ว่าshกระบวนการนี้จะใช้งานได้จนกว่าล่ามจะทำงานเสร็จ ข้อเสนอแนะหรือข้อเสนอแนะใด ๆ ที่จะได้รับการชื่นชม


1
ที่เกี่ยวข้อง: unix.stackexchange.com/questions/63979/…
Kusalananda

คำตอบ:


27

ไม่มีการแก้ปัญหาทั่วไปเป็นอย่างน้อยไม่ได้ถ้าคุณต้องการที่จะสนับสนุนลินุกซ์เพราะเคอร์เนลถือว่าทุกอย่างต่อไปนี้ครั้งแรก“คำว่า” ในบรรทัด shebang เป็นอาร์กิวเมนต์เดียว

ฉันไม่แน่ใจว่าข้อ จำกัด ของ NixOS คืออะไร แต่โดยทั่วไปแล้วฉันจะเขียน Shebang ของคุณเป็น

#!/bin/bash --posix

หรือหากเป็นไปได้ให้ตั้งค่าตัวเลือกในสคริปต์ :

set -o posix

หรือคุณสามารถให้สคริปต์รีสตาร์ทตัวเองด้วยการเรียกใช้เชลล์ที่เหมาะสม:

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

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

GNU coreutils' envมีวิธีการแก้ไขตั้งแต่เวอร์ชัน 8.30 ดูคำตอบของunodeเพื่อดูรายละเอียด (สามารถใช้ได้ใน Debian 10 และใหม่กว่า, RHEL 8 และใหม่กว่า, Ubuntu 19.04 และใหม่กว่าเป็นต้น)


18

แม้ว่าจะไม่ใช่แบบพกพา แต่เริ่มต้นด้วย coreutils 8.30 และตามเอกสารคุณจะสามารถใช้:

#!/usr/bin/env -S command arg1 arg2 ...

ได้รับ:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

คุณจะได้รับ:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

และในกรณีที่คุณอยากรู้showargsคือ:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done

สิ่งนี้เป็นสิ่งที่ดีมากสำหรับการอ้างอิงในอนาคต
John McGehee

ตัวเลือกนั้นถูกคัดลอกมาจาก FreeBSD's envซึ่ง-Sถูกเพิ่มเข้ามาในปี 2005 ดูlists.gnu.org/r/coreutils/2018-04/msg00011.html
Stéphane Chazelas

ทำงานกับ Fedora 29
Eric

@ เปิดใช้การปรับปรุงบางอย่างของshowargs: pastebin.com/q9m6xr8Hและpastebin.com/gS8AQ5WA (หนึ่งซับ)
Eric

FYI: จาก coreutils 8.31 envรวมถึงตัวของมันเองshowargs: ตัวเลือก -vเช่น#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy

9

มาตรฐาน POSIX สั้นมากในการอธิบาย#!:

จากส่วนเหตุผลของเอกสารของexec()ตระกูลของส่วนต่อประสานระบบ :

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

จากหัวข้อShell Shell :

เชลล์อ่านอินพุตจากไฟล์ (ดูsh) จาก-cตัวเลือกหรือจากsystem()และpopen()ฟังก์ชั่นที่กำหนดไว้ในระดับเสียงของอินเตอร์เฟซระบบของ POSIX.1-2008 ถ้าบรรทัดแรกของไฟล์ของคำสั่งเชลล์เริ่มต้นด้วยตัวอักษร#!ผลลัพธ์ที่ไม่ระบุรายละเอียด

นี่หมายความว่าโดยทั่วไปแล้วการติดตั้งใด ๆ (Unix ที่คุณใช้) มีอิสระที่จะทำแบบเฉพาะเจาะจงในการแยกบรรทัด Shebang ตามที่ต้องการ

Unices บางอันเช่น macOS (ไม่สามารถทดสอบ ATM) จะแบ่งอาร์กิวเมนต์ที่กำหนดให้กับล่ามในบรรทัด shebang เป็นอาร์กิวเมนต์แยกขณะที่ Linux และ Unices อื่น ๆ ส่วนใหญ่จะให้อาร์กิวเมนต์เป็นตัวเลือกเดียวกับล่าม

ดังนั้นจึงไม่ฉลาดนักที่จะใช้สาย shebang ที่สามารถโต้แย้งมากกว่าหนึ่งข้อได้

เห็นแล้วยังส่วนการพกพาของบทความ shebang วิกิพีเดีย


วิธีแก้ปัญหาอย่างง่ายวิธีหนึ่งซึ่งใช้งานทั่วไปสำหรับยูทิลิตี้หรือภาษาใด ๆ คือการสร้างสคริปต์ตัวตัดที่เรียกใช้งานสคริปต์จริงด้วยอาร์กิวเมนต์บรรทัดคำสั่งที่เหมาะสม:

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

ฉันไม่คิดว่าฉันจะพยายามทำให้ตัวเองกลับมาทำงานอีกครั้งเพราะรู้สึกค่อนข้างบอบบาง


7

shebang อธิบายไว้ในexecve(2) man page ดังต่อไปนี้:

#! interpreter [optional-arg]

ยอมรับสองช่องว่างในไวยากรณ์นี้:

  1. ช่องว่างหนึ่งหน้าเส้นทางของล่ามแต่ช่องว่างนี้เป็นทางเลือก
  2. ช่องว่างหนึ่งช่องแยกระหว่างเส้นทางของล่ามและอาร์กิวเมนต์ที่เป็นทางเลือก

หมายเหตุว่าผมไม่ได้ใช้เป็นพหูพจน์เมื่อพูดของอาร์กิวเมนต์ตัวเลือกไม่ไม่ไวยากรณ์ข้างต้นใช้[optional-arg ...]เช่นคุณสามารถให้มากที่สุดอาร์กิวเมนต์เดียว

ตราบใดที่การเขียนสคริปต์เชลล์เกี่ยวข้องคุณสามารถใช้setคำสั่งในตัวใกล้กับจุดเริ่มต้นของสคริปต์ซึ่งจะอนุญาตให้ตั้งค่าพารามิเตอร์ล่ามโดยให้ผลลัพธ์เช่นเดียวกับถ้าคุณใช้อาร์กิวเมนต์บรรทัดคำสั่ง

ในกรณีของคุณ:

set -o posix

จากพรอมต์ Bash ให้ตรวจสอบเอาต์พุตของhelp setเพื่อรับตัวเลือกที่มีทั้งหมด


1
คุณได้รับอนุญาตให้มีมากกว่าสองช่องว่างพวกเขากำลังถือว่าเป็นส่วนหนึ่งของอาร์กิวเมนต์ที่เป็นตัวเลือก
Stephen Kitt

@StephenKitt: จริง ๆ แล้วพื้นที่สีขาวที่นี่จะต้องถูกยึดเป็นหมวดหมู่มากกว่า char space จริง ฉันคิดว่าช่องว่างสีขาวอื่น ๆ เช่นแท็บควรได้รับการยอมรับอย่างกว้างขวางเช่นกัน
WhiteWinterWolf

3

บน Linux Shebang นั้นมีความยืดหยุ่นไม่มาก ตามคำตอบหลายคำตอบ (คำตอบของสตีเฟ่นคิตต์และของJörg W Mittag ) ไม่มีวิธีที่กำหนดไว้ในการส่งข้อโต้แย้งหลาย ๆ อย่างในแนว Shebang

ฉันไม่แน่ใจว่าจะเป็นประโยชน์กับทุกคนหรือไม่ แต่ฉันเขียนสคริปต์สั้น ๆ เพื่อใช้คุณสมบัติที่ขาดหายไป ดูhttps://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa

นอกจากนี้ยังสามารถเขียนวิธีแก้ไขแบบฝังได้ ซอลเบลโลว์ฉันขอนำเสนอวิธีแก้ปัญหาผู้ไม่เชื่อเรื่องภาษาสี่ประการที่นำไปใช้กับสคริปต์ทดสอบเดียวกันและผลการพิมพ์แต่ละครั้ง /tmp/shebangฉันคิดว่าสคริปต์เป็นปฏิบัติการและอยู่ใน


การตัดสคริปต์ของคุณใน bash heredoc ภายในการทดแทนกระบวนการ

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

#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv
try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"

โทรecho -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'พิมพ์:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /dev/fd/62
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END

โปรดทราบว่าการทดแทนกระบวนการผลิตไฟล์พิเศษ สิ่งนี้อาจไม่เหมาะกับไฟล์เรียกใช้งานทั้งหมด ตัวอย่างเช่น#!/usr/bin/lessบ่น:/dev/fd/63 is not a regular file (use -f to see it)

ฉันไม่ทราบว่าเป็นไปได้หรือไม่ที่จะมี heredoc อยู่ภายในการทดแทนกระบวนการอย่างรวดเร็ว


การตัดสคริปต์ของคุณใน heredoc ง่าย ๆ

สั้นลงและเรียบง่ายขึ้น แต่คุณจะไม่สามารถเข้าถึงได้stdinจากสคริปต์ของคุณและมันต้องการล่ามเพื่อให้สามารถอ่านและรันสคริปต์stdinได้

#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER

โทรecho -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'พิมพ์:

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END

ใช้การsystem()โทรawk แต่ไม่มีอาร์กิวเมนต์

ส่งผ่านชื่อไฟล์ที่ถูกเรียกใช้งานอย่างถูกต้อง แต่สคริปต์ของคุณจะไม่ได้รับอาร์กิวเมนต์ที่คุณให้ โปรดทราบว่า awk เป็นภาษาเดียวที่ฉันรู้ว่าทั้งสองมีล่ามติดตั้งบน linux โดยค่าเริ่มต้นและอ่านคำแนะนำจากบรรทัดคำสั่งโดยค่าเริ่มต้น

#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

โทรecho -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'พิมพ์:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

ใช้การsystem()โทรawk 4.1 ขึ้นไปโดยที่อาร์กิวเมนต์ของคุณไม่มีช่องว่าง

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

#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

โทรecho -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'พิมพ์:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END

สำหรับ awk รุ่นต่ำกว่า 4.1 คุณจะต้องใช้สตริงภายในสำหรับวงดูฟังก์ชั่นเช่นhttps://www.gnu.org/software/gawk/manual/html_node/Join-Function.html


1
อ้างอิง terminator เอกสารที่นี่เพื่อยับยั้ง$variableหรือ`command`ทดแทน:exec python3 -O <(cat <<'EOWRAPPER'
John McGehee

2

เคล็ดลับในการใช้งานLD_LIBRARY_PATHกับไพ ธ อนใน#!บรรทัด (shebang) ที่ไม่ได้ขึ้นอยู่กับสิ่งอื่นใดนอกจากเปลือกและใช้งานได้:

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

ตามที่อธิบายไว้ที่อื่นในหน้านี้เชลล์บางตัวshสามารถใช้สคริปต์กับอินพุตมาตรฐาน

สคริปต์ที่เราให้ความshพยายามที่จะดำเนินการคำสั่ง''''ซึ่งจะง่ายไป''(สตริงว่างเปล่า) โดยshและแน่นอนว่ามันล้มเหลวในการดำเนินการได้ที่ไม่มี''คำสั่งจึงได้ตามปกติเอาท์พุทline 2: command not foundในการให้คำอธิบายถึงข้อผิดพลาดมาตรฐาน แต่เราเปลี่ยนเส้นทางข้อความนี้ใช้2>/dev/nullกับ หลุมดำที่อยู่ใกล้ที่สุดเพราะมันจะยุ่งและสับสนกับผู้ใช้เพื่อให้shมันแสดง

จากนั้นเราจะดำเนินการกับคำสั่งที่น่าสนใจสำหรับเรา: execซึ่งจะแทนที่กระบวนการเชลล์ปัจจุบันตามสิ่งต่อไปนี้ในกรณีของเรา: /usr/bin/env pythonด้วยพารามิเตอร์ที่เพียงพอ:

  • "$0" เพื่อให้ไพ ธ อนรู้ว่าสคริปต์ควรเปิดและตีความและตั้งค่าอย่างไร sys.argv[0]
  • "$@"เพื่อตั้งค่า python ของsys.argv[1:]อาร์กิวเมนต์ที่ส่งผ่านบนบรรทัดคำสั่งสคริปต์

และเรายังขอenvให้ตั้งค่าLD_LIBRARY_PATHตัวแปรสภาพแวดล้อมซึ่งเป็นจุดเดียวของแฮ็ค

คำสั่งเปลือกปลายที่ความคิดเห็นที่เริ่มต้นด้วยเพื่อให้เปลือกละเว้นต่อท้ายคำพูดที่สาม#'''

shจะถูกแทนที่ด้วยอินสแตนซ์ใหม่มันเมล็ดของล่ามไพ ธ อนซึ่งเปิดและอ่านสคริปต์ต้นฉบับไพ ธ อนที่ได้รับเป็นอาร์กิวเมนต์แรก ( "$0")

Python เปิดไฟล์และข้ามบรรทัดแรกของแหล่งที่มาด้วย-xอาร์กิวเมนต์ หมายเหตุ: มันยังใช้งานได้โดยไม่ต้อง-xเพราะหลาม shebang เป็นเพียงความคิดเห็น

Python แปลความหมายของบรรทัดที่ 2 เป็น docstring สำหรับไฟล์โมดูลปัจจุบันดังนั้นหากคุณต้องการ docstring ของโมดูลที่ถูกต้องให้ตั้งค่า__doc__สิ่งแรกในโปรแกรม python ของคุณดังตัวอย่างด้านบน



ระบุว่าสตริงว่างคือ…อืม…ว่างเปล่าคุณควรจะทิ้งคำสั่งของคุณไม่พบธุรกิจลิง: ''''exec ...ควรทำงานให้เสร็จ หมายเหตุไม่มีช่องว่างก่อน exec หรือมันจะทำให้มองหาคำสั่งที่ว่างเปล่า คุณต้องการที่จะประกบกันที่ว่างเปล่าบนหาเรื่องแรกดังนั้นเพื่อให้เป็น$0 exec
Caleb

1

ฉันพบวิธีแก้ปัญหาที่ค่อนข้างโง่เมื่อมองหาไฟล์ปฏิบัติการที่ยกเว้นสคริปต์เป็นอาร์กิวเมนต์เดียว:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.