Tại sao lại là những người nổi tiếng thế giới / bin / sh - shebang?


27
#! / 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/shhay #!/bin/sh?

Cái đó -để làm gì?

Câu trả lời:


37

Đó 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ó .txttệ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>

argluô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-scriptthườ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-scriptmà 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 -eufcũ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:

  1. không ai đầu óc bình thường sẽ gọi một kịch bản với một cái gì đó bắt đầu với -hoặc +hoặc đặt chúng trong một thư mục có tên bắt đầu bằng -hoặc +.
  2. ngay cả khi họ đã làm, trước tiên người ta sẽ lập luận rằng họ chỉ có thể tự trách mình, nhưng cũng vậy, khi bạn gọi một tập lệnh, thường xuyên hơn không, đó là từ trình bao hoặc từ 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 $PATHtrườ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-scriptthể bạn muốn the-scripttrong 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 binquyề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-scriptvà 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ố shtriển khai như một số ksh88người dựa trên sẽ tra cứu lý luận kịch bản mà không /$PATHmặ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 -ftù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-(trong trường hợp đó, với hầu hết sed/ awktriển khai, sed/ awkkhô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


1
Điều này có liên quan gì đến #! / Bin / bash không?
Joe

1
@Joe, tất nhiên, bashchấ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, bashchấp nhận cả #! /bin/bash -#! /bin/bash --.
Stéphane Chazelas
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.