Nhiều đối số trong shebang


32

Tôi tự hỏi liệu có một cách chung để chuyển nhiều tùy chọn cho một tệp thực thi thông qua dòng shebang ( #!).

Tôi sử dụng NixOS, và phần đầu tiên của shebang trong bất kỳ kịch bản nào tôi viết thường là /usr/bin/env. Vấn đề tôi gặp phải sau đó là mọi thứ xuất hiện sau đó đều được hệ thống hiểu là một tệp hoặc thư mục.

Ví dụ, giả sử rằng tôi muốn viết một tập lệnh sẽ được thực thi bằng bashchế độ posix. Cách viết ngây thơ của shebang sẽ là:

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

nhưng cố gắng thực thi tập lệnh kết quả sẽ tạo ra lỗi sau:

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

Tôi biết về bài đăng này , nhưng tôi đã tự hỏi liệu có một giải pháp tổng quát và sạch sẽ hơn.


EDIT : Tôi biết rằng đối với các tập lệnh Guile , có một cách để đạt được những gì tôi muốn, được ghi lại trong Phần 4.3.4 của hướng dẫn:

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

Thủ thuật ở đây là dòng thứ hai (bắt đầu bằng exec) được hiểu là mã bởi shnhưng, nằm trong #!... !#khối, như một nhận xét, và do đó bị bỏ qua bởi trình thông dịch Guile.

Nó sẽ không thể khái quát phương pháp này cho bất kỳ thông dịch viên?


EDIT thứ hai : Sau khi chơi xung quanh một chút, có vẻ như, đối với các thông dịch viên có thể đọc dữ liệu đầu vào của họ stdin, phương thức sau sẽ hoạt động:

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

Tuy nhiên, có lẽ nó không tối ưu vì shquá trình tồn tại cho đến khi người phiên dịch hoàn thành công việc. Bất kỳ thông tin phản hồi hoặc đề nghị sẽ được đánh giá cao.



Câu trả lời:


27

Không có giải pháp chung, ít nhất là không nếu bạn cần hỗ trợ Linux, bởi vì nhân Linux xử lý tất cả mọi thứ theo sau từ đầu tiên trong dòng shebang như một đối số duy nhất .

Tôi không chắc ràng các ràng buộc của NixOS là gì, nhưng thông thường tôi sẽ chỉ viết shebang của bạn là

#!/bin/bash --posix

hoặc, nếu có thể, hãy đặt các tùy chọn trong tập lệnh :

set -o posix

Ngoài ra, bạn có thể tự khởi động lại tập lệnh với lời gọi shell thích hợp:

#!/bin/sh -

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

shift

# Processing continues

Cách tiếp cận này có thể được khái quát cho các ngôn ngữ khác, miễn là bạn tìm ra cách để một vài dòng đầu tiên (được giải thích bởi shell) sẽ bị bỏ qua bởi ngôn ngữ đích.

GNU coreutils' envcung cấp một cách giải quyết kể từ phiên bản 8.30, xem câu trả lời của Unode để biết chi tiết. (Điều này có sẵn trong Debian 10 trở lên, RHEL 8 trở lên, Ubuntu 19.04 trở lên, v.v.)


18

Mặc dù không chính xác di động, bắt đầu với coreutils 8.30 và theo tài liệu của nó, bạn sẽ có thể sử dụng:

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

Vì vậy, đưa ra:

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

bạn sẽ nhận được:

% ./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'

và trong trường hợp bạn tò mò showargslà:

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

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

Điều này là rất tốt để biết để tham khảo trong tương lai.
John McGehee

Tùy chọn đó đã được sao chép từ FreeBSD, envnơi -Sđã được thêm vào năm 2005. Xem danh sách.gnu.org / r / coreutils / 2018-04 / msg00011.html
Stéphane Chazelas

Làm việc với Fedora 29
Eric

@unode một số cải tiến của showargs: pastebin.com/q9m6xr8Hpastebin.com/gS8AQ5WA (một lớp)
Eric

FYI: kể từ coreutils 8.31, envbao gồm chính nó showargs: tùy chọn -v, vd#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy

9

Tiêu chuẩn POSIX rất ngắn gọn về mô tả #!:

Từ phần cơ sở của tài liệu về exec()họ giao diện hệ thống :

Một cách khác mà một số triển khai lịch sử xử lý các tập lệnh shell là nhận ra hai byte đầu tiên của tệp là chuỗi ký tự #!và sử dụng phần còn lại của dòng đầu tiên của tệp làm tên của trình thông dịch lệnh để thực thi.

Từ phần Giới thiệu Shell :

Shell đọc đầu vào của nó từ một tệp (xem sh), từ -ctùy chọn hoặc từ system()và các popen()chức năng được xác định trong khối Giao diện hệ thống của POSIX.1-2008. Nếu dòng đầu tiên của một tệp lệnh shell bắt đầu bằng các ký tự #!, kết quả sẽ không được chỉ định .

Điều này về cơ bản có nghĩa là bất kỳ triển khai nào (Unix bạn đang sử dụng) đều miễn phí để thực hiện các chi tiết cụ thể về phân tích cú pháp của dòng shebang như nó muốn.

Một số Unice, như macOS (không thể kiểm tra ATM), sẽ chia các đối số được cung cấp cho trình thông dịch trên dòng shebang thành các đối số riêng biệt, trong khi Linux và hầu hết các Unice khác sẽ cung cấp các đối số dưới dạng một tùy chọn cho trình thông dịch.

Do đó, thật không khôn ngoan khi dựa vào dòng shebang có thể nhận nhiều hơn một đối số.

Xem thêm phần Tính di động của bài viết Shebang trên Wikipedia .


Một giải pháp dễ dàng, có thể khái quát hóa cho bất kỳ tiện ích hoặc ngôn ngữ nào, là tạo một tập lệnh bao bọc thực thi tập lệnh thực với các đối số dòng lệnh thích hợp:

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

Tôi không nghĩ rằng cá nhân tôi sẽ cố gắng làm cho nó tự thực hiện lại vì cảm thấy hơi mong manh.


7

Shebang được mô tả trong execve(2) trang man như sau:

#! interpreter [optional-arg]

Hai khoảng trắng được chấp nhận trong cú pháp này:

  1. Một khoảng trắng trước đường dẫn trình thông dịch , nhưng khoảng trắng này là tùy chọn.
  2. Một không gian ngăn cách đường dẫn trình thông dịch và đối số tùy chọn của nó.

Lưu ý rằng tôi đã không sử dụng số nhiều khi nói về một đối số tùy chọn, cú pháp ở trên cũng không sử dụng [optional-arg ...], vì bạn có thể cung cấp tối đa một đối số .

Đối với kịch bản shell có liên quan, bạn có thể sử dụng setlệnh tích hợp gần đầu tập lệnh sẽ cho phép đặt tham số trình thông dịch, cung cấp kết quả giống như khi bạn sử dụng đối số dòng lệnh.

Trong trường hợp của bạn:

set -o posix

Từ một dấu nhắc Bash, kiểm tra đầu ra của help setđể có được tất cả các tùy chọn có sẵn.


1
Bạn được phép có nhiều hơn hai khoảng trắng, chúng chỉ được coi là một phần của đối số tùy chọn.
Stephen Kitt

@StephenKitt: Thật vậy, không gian trắng ở đây sẽ được coi là một danh mục hơn là không gian char thực tế. Tôi cho rằng các khoảng trắng khác như tab cũng nên được chấp nhận rộng rãi.
WhiteWinterWolf

3

Trên Linux, shebang không linh hoạt; theo nhiều câu trả lời (câu trả lời của Stephen KittJörg W Mittag's ), không có cách nào được chỉ định để vượt qua nhiều đối số trong một dòng shebang.

Tôi không chắc liệu nó có được sử dụng cho bất kỳ ai không, nhưng tôi đã viết một đoạn script ngắn để thực hiện tính năng thiếu. Xem https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .

Cũng có thể viết cách giải quyết nhúng. Dưới đây, tôi trình bày bốn cách giải quyết ngôn ngữ không thể áp dụng cho cùng một kịch bản kiểm tra và kết quả mỗi bản in. Tôi cho rằng kịch bản có thể thực thi được và nằm trong /tmp/shebang.


Gói kịch bản của bạn trong một bash heredoc bên trong quá trình thay thế

Theo như tôi biết, đây là cách làm ngôn ngữ đáng tin cậy nhất. Nó cho phép vượt qua các đối số và bảo tồn stdin. Hạn chế là trình thông dịch không biết vị trí (thực) của tệp mà nó đọc.

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

Gọi echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'in:

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

Lưu ý rằng quá trình thay thế tạo ra một tập tin đặc biệt. Điều này có thể không phù hợp với tất cả các thực thi. Chẳng hạn, #!/usr/bin/lessphàn nàn:/dev/fd/63 is not a regular file (use -f to see it)

Tôi không biết nếu có thể có di truyền trong quá trình thay thế trong dấu gạch ngang.


Gói kịch bản của bạn trong một di sản đơn giản

Ngắn hơn và đơn giản hơn, nhưng bạn sẽ không thể truy cập stdintừ tập lệnh của mình và nó yêu cầu trình thông dịch có thể đọc và thực thi tập lệnh từ đó 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

Gọi echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'in:

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

Sử dụng system()cuộc gọi awk nhưng không có đối số

Chuyển chính xác tên của tệp được thực thi, nhưng tập lệnh của bạn sẽ không nhận được các đối số bạn đưa ra. Lưu ý rằng awk là ngôn ngữ duy nhất tôi biết có cả hai trình thông dịch được cài đặt trên linux theo mặc định và đọc các hướng dẫn của nó từ dòng lệnh theo mặc định.

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

Gọi echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'in:

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

Sử dụng system()cuộc gọi awk 4.1+ , miễn là các đối số của bạn không chứa khoảng trắng

Đẹp, nhưng chỉ khi bạn chắc chắn rằng tập lệnh của bạn sẽ không được gọi với các đối số có chứa khoảng trắng. Như bạn có thể thấy, các đối số của bạn chứa khoảng trắng sẽ được phân tách, trừ khi các khoảng trắng được thoát.

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

Gọi echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'in:

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

Đối với các phiên bản awk dưới 4.1, bạn sẽ phải sử dụng nối chuỗi trong vòng lặp for, xem hàm ví dụ https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .


1
Trích dẫn tài liệu chấm dứt ở đây để ức chế $variablehoặc `command`thay thế:exec python3 -O <(cat <<'EOWRAPPER'
John McGehee

2

Một mẹo để sử dụng LD_LIBRARY_PATHvới trăn trên dòng #!(shebang) không phụ thuộc vào bất cứ thứ gì khác ngoài vỏ và thực hiện một điều trị:

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

__doc__ = 'A great module docstring'

Như đã giải thích ở những nơi khác trong trang này, một số shell như shcó thể lấy một tập lệnh trên đầu vào tiêu chuẩn của chúng.

Các kịch bản chúng tôi cung cấp shcố gắng để thực hiện lệnh ''''đó được đơn giản hóa đến ''(chuỗi rỗng) bằng shvà tất nhiên nó không thành công để thực hiện nó như là không có ''lệnh, vì vậy nó thường được kết quả đầu ra line 2: command not foundtrên mô tả sai số chuẩn nhưng chúng tôi chuyển hướng thông điệp này sử dụng 2>/dev/nullđến lỗ đen gần nhất bởi vì nó sẽ lộn xộn và gây nhầm lẫn cho người dùng để shhiển thị nó.

Sau đó, chúng tôi tiến hành lệnh quan tâm đến chúng tôi: execthay thế quy trình shell hiện tại bằng cách tiếp theo, trong trường hợp của chúng tôi: /usr/bin/env pythonvới các tham số đầy đủ:

  • "$0" để cho python biết nó nên mở và diễn giải kịch bản nào, đồng thời thiết lập sys.argv[0]
  • "$@"để đặt python's sys.argv[1:]cho các đối số được truyền trên dòng lệnh script.

Và chúng tôi cũng yêu cầu envthiết lập LD_LIBRARY_PATHbiến môi trường, đó là điểm duy nhất của hack.

Lệnh shell kết thúc tại nhận xét bắt đầu bằng #để shell bỏ qua dấu ngoặc kép '''.

shsau đó được thay thế bằng một phiên bản mới của trình thông dịch python mở và đọc tập lệnh nguồn python được đưa ra làm đối số đầu tiên (the "$0").

Python mở tệp và bỏ qua dòng thứ nhất của nguồn nhờ vào -xđối số. Lưu ý: nó cũng hoạt động mà không cần -xvì Python là shebang chỉ là một nhận xét .

Sau đó, Python diễn giải dòng thứ 2 là chuỗi doc cho tệp mô-đun hiện tại, vì vậy nếu bạn cần một chuỗi mô-đun hợp lệ, chỉ cần đặt __doc__điều đầu tiên trong chương trình python của bạn như trong ví dụ trên.



Cho rằng một chuỗi rỗng là trống um, bạn sẽ có thể bỏ lệnh không tìm thấy doanh nghiệp khỉ: ''''exec ...nên hoàn thành công việc. Lưu ý không có khoảng trắng trước khi thực hiện hoặc nó sẽ làm cho nó tìm kiếm lệnh trống. Bạn muốn ghép trống vào arg đầu tiên để cái $0exec.
Caleb

1

Tôi đã tìm thấy một cách giải quyết khá ngu ngốc khi tìm kiếm một tệp thực thi chấp nhận tập lệnh dưới dạng một đối số duy nhất:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
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.