#! / thùng / sh -
Là (hoặc ít nhất là) thường là shebang được đề xuất để có một kịch bản được giải thích bởi /bin/sh
.
Tại sao không chỉ #! /bin/sh
hay #!/bin/sh
?
Cái đó -
để làm gì?
#! / thùng / sh -
Là (hoặc ít nhất là) thường là shebang được đề xuất để có một kịch bản được giải thích bởi /bin/sh
.
Tại sao không chỉ #! /bin/sh
hay #!/bin/sh
?
Cái đó -
để làm gì?
Câu trả lời:
Đó là vì một lý do tương tự như lý do tại sao bạn cần viết:
rm -- *.txt
Và không
rm *.txt
Trừ khi bạn có thể đảm bảo không có .txt
tệp nào trong thư mục hiện tại có tên bắt đầu bằng -
.
Trong:
rm <arg>
<arg>
được coi là một tùy chọn nếu nó bắt đầu bằng -
hoặc một tệp để loại bỏ. Trong
rm - <arg>
arg
luôn được coi là một tập tin để loại bỏ bất kể nó bắt đầu bằng -
hay không.
Điều đó cũng tương tự đối với sh
.
Khi một người thực thi một kịch bản bắt đầu bằng
#! /bin/sh
Điển hình với:
execve("path/to/the-script", ["the-script", "arg"], [environ])
Hệ thống biến đổi nó thành:
execve("/bin/sh", ["/bin/sh", "path/to/the-script", "arg"], [environ])
Đây path/to/the-script
thường không phải là một cái gì đó nằm dưới sự kiểm soát của tác giả kịch bản. Tác giả không thể dự đoán nơi các bản sao của tập lệnh sẽ được lưu trữ cũng như dưới tên nào. Cụ thể, họ không thể đảm bảo rằng cái path/to/the-script
mà nó được gọi là sẽ không bắt đầu bằng -
(hoặc +
đó cũng là một vấn đề với sh
). Đó là lý do tại sao chúng ta cần các -
đây để đánh dấu sự kết thúc tùy chọn.
Chẳng hạn, trên hệ thống của tôi zcat
(giống như hầu hết các tập lệnh khác thực sự) là một ví dụ về tập lệnh không tuân theo khuyến nghị đó:
$ head -n1 /bin/zcat
#!/bin/sh
$ mkdir +
$ ln -s /bin/zcat +/
$ +/zcat
/bin/sh: +/: invalid option
[...]
Bây giờ bạn có thể hỏi tại sao #! /bin/sh -
và không #! /bin/sh --
?
Mặc dù #! /bin/sh --
sẽ hoạt động với đạn POSIX, nhưng #! /bin/sh -
nó dễ mang theo hơn; đặc biệt là các phiên bản cổ xưa của sh
. sh
đối xử -
như trước và cuối tùy chọn getopt()
và việc sử dụng chung --
để đánh dấu sự kết thúc của các tùy chọn trong một thời gian dài. Cách trình bao Bourne (từ cuối những năm 70) phân tích cú pháp các đối số của nó, chỉ đối số đầu tiên được xem xét cho các tùy chọn nếu nó bắt đầu bằng -
. Tất cả các ký tự sau -
sẽ được coi là tên tùy chọn; nếu không có nhân vật nào sau -
đó, thì không có lựa chọn nào khác. Điều đó bị mắc kẹt và tất cả các vỏ giống như Bourne sau đó nhận ra -
như một cách để đánh dấu sự kết thúc của các tùy chọn.
Trong hệ vỏ Bourne (nhưng không phải trong hệ vỏ Bourne hiện đại), #! /bin/sh -euf
cũng sẽ giải quyết vấn đề vì chỉ có đối số đầu tiên được xem xét cho các tùy chọn.
Bây giờ, người ta có thể nói rằng chúng ta đang trở thành nhà mô phạm ở đây, và đó cũng là lý do tại sao tôi viết nhu cầu in nghiêng ở trên:
-
hoặc +
hoặc đặt chúng trong một thư mục có tên bắt đầu bằng -
hoặc +
.execvp()
/ execlp()
-type. Và trong trường hợp đó, nói chung, bạn gọi chúng hoặc the-script
để tìm kiếm trong $PATH
trường hợp đó, đối số đường dẫn đến lệnh execve()
gọi hệ thống thường sẽ bắt đầu bằng /
(không -
phải +
) hoặc như ./the-script
thể bạn muốn the-script
trong thư mục hiện tại được chạy (và sau đó đường dẫn bắt đầu bằng ./
, không phải -
cũng không +
).Bây giờ bên cạnh vấn đề chính xác về mặt lý thuyết , có một lý do khác tại sao được #! /bin/sh -
khuyến nghị là thực hành tốt . Và điều đó quay trở lại thời điểm mà một số hệ thống vẫn hỗ trợ các tập lệnh setuid.
Nếu bạn có một tập lệnh chứa:
#! /bin/sh
/bin/echo "I'm running as root"
Và tập lệnh đó là root setuid (như có -r-sr-xr-x root bin
quyền), trên các hệ thống đó, khi được thực thi bởi một người dùng thông thường,
execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])
sẽ được thực hiện như root
!
Nếu người dùng tạo một liên kết tượng trưng /tmp/-i -> path/to/the-script
và thực hiện nó như là -i
, thì nó sẽ bắt đầu một shell tương tác ( /bin/sh -i
) như root
.
Các -
sẽ làm việc xung quanh đó (nó sẽ không làm việc xung quanh vấn đề chủng tộc điều kiện , hoặc thực tế là một số sh
triển khai như một số ksh88
người dựa trên sẽ tra cứu lý luận kịch bản mà không /
ở $PATH
mặc dù).
Ngày nay, hầu như không có hệ thống nào hỗ trợ tập lệnh setuid nữa và một số trong số đó vẫn làm (thường không phải theo mặc định), cuối cùng thực hiện một execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg])
( <n>
mô tả tệp mở để đọc trên tập lệnh) hoạt động xung quanh cả vấn đề này và điều kiện của cuộc đua.
Lưu ý rằng bạn gặp các vấn đề tương tự với hầu hết các thông dịch viên, không chỉ các shell giống như Bourne. Các shell không giống Bourne thường không hỗ trợ -
làm điểm đánh dấu kết thúc tùy chọn, nhưng thường hỗ trợ --
thay thế (ít nhất là cho các phiên bản hiện đại).
Cũng thế
#! /usr/bin/awk -f
#! /usr/bin/sed -f
Không có vấn đề vì đối số tiếp theo được coi là đối số cho -f
tùy chọn trong mọi trường hợp, nhưng nó vẫn không hoạt động nếu path/to/script
là -
(trong trường hợp đó, với hầu hết sed
/ awk
triển khai, sed
/ awk
không đọc mã từ -
tập tin, nhưng từ stdin thay thế).
Cũng lưu ý rằng trên hầu hết các hệ thống, người ta không thể sử dụng:
#! /usr/bin/env sh -
#! /usr/bin/perl -w --
Như trên hầu hết các hệ thống, cơ chế shebang chỉ cho phép một đối số sau đường dẫn trình thông dịch.
Về việc có nên sử dụng #! /bin/sh -
vs #!/bin/sh -
, đó chỉ là vấn đề của hương vị. Tôi thích cái trước vì nó làm cho đường dẫn trình thông dịch rõ hơn và làm cho việc chọn chuột dễ dàng hơn. Có một truyền thuyết nói rằng không gian là cần thiết trong một số phiên bản Unix cổ đại nhưng AFAIK, chưa bao giờ được xác minh.
Một tài liệu tham khảo rất hay về Unix shebang có thể được tìm thấy tại https://www.in-ulm.de/~mascheck/various/shebang
bash
chấp nhận các tùy chọn như mọi shell khác. Là một vỏ giống như Bourne và POSIX, bash
chấp nhận cả #! /bin/bash -
và #! /bin/bash --
.