Liệu shebang xác định shell chạy script?


84

Đây có thể là một câu hỏi ngớ ngẩn, nhưng tôi vẫn hỏi nó. Nếu tôi đã tuyên bố một shebang

#!/bin/bash 

vào đầu my_shell_script.sh, vì vậy tôi luôn phải gọi tập lệnh này bằng cách sử dụng bash

[my@comp]$bash my_shell_script.sh

hoặc tôi có thể sử dụng ví dụ

[my@comp]$sh my_shell_script.sh

và tập lệnh của tôi xác định shell đang chạy bằng shebang? Có phải điều tương tự xảy ra với kshvỏ? Tôi đang sử dụng AIX.


6
Có một chút nhầm lẫn về phía bạn: khi bạn thực hiện "_some_shell some_script", nó bắt đầu _some_shell và yêu cầu nó diễn giải some_script. Vì vậy, không, nếu bạn làm "sh my_shell_script.sh" thì nó sẽ không diễn giải shebang, nhưng sẽ diễn giải kịch bản theo sh. Để sử dụng shebang: chmod +x my_shell_script.sh ; /path/to/my_shell_script.sh # or ./my_shell_script.sh if you happen to be in its directory
Olivier Dulac

Câu trả lời:


117

Các công việc #! là một ví dụ có thể đọc được con người của một con số kỳ diệu bao gồm các chuỗi byte 0x23 0x21, được sử dụng bởi các exec()gia đình của các chức năng để xác định xem các tập tin được thực hiện là một kịch bản hay một nhị phân. Khi shebang có mặt, exec()sẽ chạy chương trình thực thi được chỉ định sau shebang thay thế.

Lưu ý rằng điều này có nghĩa là nếu bạn gọi một tập lệnh bằng cách chỉ định trình thông dịch trên dòng lệnh, như được thực hiện trong cả hai trường hợp được đưa ra trong câu hỏi, exec()sẽ thực thi trình thông dịch được chỉ định trên dòng lệnh, nó thậm chí sẽ không nhìn vào tập lệnh.

Vì vậy, như những người khác đã lưu ý, nếu bạn muốn exec()gọi trình thông dịch được chỉ định trên dòng shebang, tập lệnh phải có tập bit thực thi và được gọi là ./my_shell_script.sh.

Hành vi rất dễ chứng minh với tập lệnh sau:

#!/bin/ksh
readlink /proc/$$/exe

Giải trình:

  • #!/bin/kshđịnh nghĩa kshlà thông dịch viên.

  • $$ giữ PID của quá trình hiện tại.

  • /proc/pid/exe là một liên kết đến thực thi của quá trình (ít nhất là trên Linux; trên AIX, /proc/$$/object/a.out là một liên kết đến tệp thực thi).

  • readlink sẽ xuất giá trị của liên kết tượng trưng.

Thí dụ:

Lưu ý : Tôi đang trình diễn điều này trên Ubuntu, trong đó trình bao mặc định /bin/shlà liên kết tượng trưng cho dấu gạch ngang tức là /bin/dash/bin/kshlà một liên kết tượng trưng /etc/alternatives/ksh, đến lượt nó là một liên kết tượng trưng /bin/pdksh.

$ chmod +x getshell.sh
$ ./getshell.sh 
/bin/pdksh
$ bash getshell.sh 
/bin/bash
$ sh getshell.sh 
/bin/dash

cảm ơn Thomas vì câu trả lời này Giả sử chúng tôi khởi chạy tập lệnh dưới dạng một tiến trình con từ Node.js hoặc Java hoặc bất cứ điều gì. Chúng ta có thể khởi chạy một quá trình "exec", và sau đó exec sẽ chạy script shell? Tôi hỏi beause Tôi đang tìm câu trả lời cho câu hỏi này: stackoverflow.com/questions/41067872/ dọa
Alexander Mills

1
@AlexanderMills Lệnh exec()được nhắc đến trong câu trả lời này là một cuộc gọi hệ thống, lệnh execlà một hàm dựng sẵn, đó là lý do tại sao bạn không thể gọi một exec chương trình từ Node.js hoặc Java. Tuy nhiên, bất kỳ lệnh shell nào được gọi bởi vd Runtime.exec()trong Java cuối cùng cũng được xử lý bằng lệnh exec()gọi hệ thống.
Thomas Nyman

Huh, vâng, tôi thực sự quen thuộc với API Java mà bạn vừa đề cập, tôi tự hỏi liệu có cách nào để gọi cuộc gọi exec () cấp thấp hơn từ Node.js bằng cách nào đó
Alexander Mills

@AlexanderMills Tôi sẽ tưởng tượng child_process.{exec(),execFile(),spawn()} tất cả sẽ được thực hiện bằng cách sử dụng C exec()(thông qua process).
Thomas Nyman

10

Có nó làm. Bằng cách này, nó không phải là một câu hỏi ngớ ngẩn. Một tài liệu tham khảo cho câu trả lời của tôi là ở đây . Bắt đầu một tập lệnh với #!

  • Nó được gọi là một shebang hoặc một dòng "bang".

  • Nó không là gì ngoài con đường tuyệt đối đến trình thông dịch Bash.

  • Nó bao gồm một ký hiệu số và ký tự dấu chấm than (#!), Theo sau là đường dẫn đầy đủ đến trình thông dịch, chẳng hạn như / bin / bash.

    Tất cả các tập lệnh trong Linux thực thi bằng trình thông dịch được chỉ định trên dòng đầu tiên Hầu như tất cả các tập lệnh bash thường bắt đầu bằng #! / Bin / bash (giả sử rằng Bash đã được cài đặt trong / bin) Điều này đảm bảo rằng Bash sẽ được sử dụng để diễn giải tập lệnh, thậm chí nếu nó được thực thi dưới vỏ khác. Shebang được giới thiệu bởi Dennis Ritchie giữa Phiên bản 7 Unix và 8 tại Phòng thí nghiệm Bell. Sau đó, nó cũng đã được thêm vào dòng BSD tại Berkeley.

Bỏ qua một dòng phiên dịch (shebang)

Nếu bạn không chỉ định một dòng trình thông dịch, mặc định thường là / bin / sh. Nhưng, bạn nên đặt dòng #! / Bin / bash.


3
Để giải thích, kernel chỉ biết cách thực thi các nhị phân được liên kết tĩnh và nơi tìm thông tin trình thông dịch cho người khác (một trường đặc biệt trong tệp nhị phân hoặc dòng shebang). Thông thường, thực thi tập lệnh shell có nghĩa là theo dòng shebang tới trình bao, sau đó theo trường DT_INTERP trong tệp nhị phân shell tới trình liên kết động.
Simon Richter

5
Cũng lưu ý rằng điều này không giới hạn ở các kịch bản shell. Tất cả các tập tin dựa trên văn bản sử dụng này. ví dụ: #!/usr/bin/perl #!/usr/local/bin/python #!/usr/local/bin/rubyMột mục nhập shebang phổ biến khác được sử dụng để hỗ trợ nhiều hệ thống là sử dụng env để định vị trình thông dịch bạn muốn sử dụng, như#!/usr/bin/env perl #!/usr/bin/env python
sambler

@sambler nói về env, nên thực sự thích? Python và Perl thường sử dụng env, trong khi trên shellscripts, điều này thường bị bỏ qua và shebang trỏ đến shell trong câu hỏi.
Polemon

1
@polemon ít trong số đó được ưa thích và nhiều hơn về các đường dẫn khác nhau. Các shell cơ bản nằm trong cùng một đường dẫn trên tất cả các hệ thống. Các phiên bản cập nhật của perl và python có thể được cài đặt ở các vị trí khác nhau trên các hệ thống khác nhau, vì vậy sử dụng env cho phép cùng một shebang luôn hoạt động, đó là lý do tại sao env được sử dụng nhiều hơn với các tập lệnh perl và python so với tập lệnh shell.
lấy mẫu

envđể tìm một chương trình trong $ PATH là một chút hack. Nó không đặt các biến môi trường như tên ngụ ý. $ PATH có thể là một kết quả khác nhau cho những người dùng khác nhau. Nhưng nó giúp các kịch bản chạy mà không cần sửa đổi trên các hệ thống đặt trình thông dịch perl hợp lý ở một số vị trí kỳ lạ.
John Mahowald

4

Cuộc execgọi hệ thống của nhân Linux hiểu rõ shebangs ( #!)

Khi bạn làm trên bash:

./something

trên Linux, điều này gọi cuộc gọi exechệ thống với đường dẫn ./something.

Dòng này của kernel được gọi trên tệp được chuyển đến exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

Nó đọc các byte đầu tiên của tệp và so sánh chúng với #!.

Nếu so sánh là đúng, thì phần còn lại của dòng được phân tích cú pháp bởi nhân Linux, điều này thực hiện một execcuộc gọi khác với đường dẫn /usr/bin/env pythonvà tệp hiện tại làm đối số đầu tiên:

/usr/bin/env python /path/to/script.py

và điều này hoạt động cho bất kỳ ngôn ngữ kịch bản sử dụng #như một nhân vật bình luận.

Và vâng, bạn có thể tạo một vòng lặp vô hạn với:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash nhận ra lỗi:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! tình cờ có thể đọc được, nhưng điều đó là không bắt buộc.

Nếu tệp bắt đầu với các byte khác nhau, thì lệnh execgọi hệ thống sẽ sử dụng một trình xử lý khác. Trình xử lý tích hợp quan trọng nhất khác dành cho các tệp thực thi ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305 để kiểm tra byte 7f 45 4c 46(cũng có thể là con người có thể đọc được .ELF). Hãy xác nhận rằng bằng cách đọc 4 byte đầu tiên /bin/ls, đây là tệp thực thi ELF:

head -c 4 "$(which ls)" | hd 

đầu ra:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

Vì vậy, khi kernel nhìn thấy các byte đó, nó sẽ lấy tệp ELF, đặt nó vào bộ nhớ một cách chính xác và bắt đầu một quy trình mới với nó. Xem thêm: https://stackoverflow.com/questions/8352535/how-does-kernel-get-an-executable-binary-file-ricky-under-linux/31394861#31394861

Cuối cùng, bạn có thể thêm trình xử lý shebang của riêng bạn với binfmt_misccơ chế. Ví dụ: bạn có thể thêm một trình xử lý tùy chỉnh cho .jarcác tệp . Cơ chế này thậm chí hỗ trợ xử lý bằng cách mở rộng tập tin. Một ứng dụng khác là chạy trong suốt các tệp thực thi của một kiến ​​trúc khác với QEMU .

Tuy nhiên, tôi không nghĩ POSIX chỉ định shebang: https://unix.stackexchange.com/a/346214/32558 , mặc dù nó đề cập đến nó trong các phần hợp lý và ở dạng "nếu các tập lệnh thực thi được hệ thống hỗ trợ có thể xảy ra".


1
Chạy ./somethingtừ một cái vỏ sẽ không vượt qua được đường dẫn đầy đủ đến exec, nhưng chính xác là đường dẫn đã đi vào. Bạn có thể sửa điều này trong câu trả lời của bạn? Làm echo "$0"trong kịch bản của bạn và bạn sẽ thấy đây là trường hợp.
AndiDog

2

Trong thực tế, nếu bạn lấy nó do đó, tệp thực thi được ghi chú trong dòng shebang, chỉ là một tệp thực thi. Nó có ý nghĩa để sử dụng một số trình thông dịch văn bản là thực thi, nhưng nó không cần thiết. Chỉ để làm rõ và trình diễn, tôi đã làm một bài kiểm tra khá vô dụng:

#!/bin/cat
useless text
more useless text
still more useless text

Được đặt tên tệp test.txt và đặt bit có thể hiển thị chmod u+x test.txt, sau đó "gọi" nó : ./test.txt. Như mong đợi, nội dung của tập tin là đầu ra. Trong trường hợp này, mèo không bỏ qua dòng shebang. Nó chỉ đơn giản là đầu ra tất cả các dòng. Do đó, bất kỳ trình thông dịch hữu ích nào cũng có thể bỏ qua dòng shebang này. Đối với bash, perl và PHP, nó chỉ đơn giản là một dòng bình luận. Vì vậy, có, những người bỏ qua dòng shebang.


-1

Từ những gì tôi thu thập được, bất cứ khi nào một tệp có tập bit thực thi và được gọi, kernel sẽ phân tích tiêu đề tệp để xác định cách tiến hành (theo như tôi biết, bạn có thể thêm trình xử lý tùy chỉnh cho các định dạng tệp tùy chỉnh thông qua LKM). Nếu tệp có vẻ là tệp văn bản có dấu #! kết hợp lúc đầu, việc thực thi của nó được gửi đến một tệp thực thi khác (thường là một lớp vỏ), một đường dẫn sẽ được chỉ định trực tiếp sau shebang đã nói, trong cùng một dòng. Nhân sau đó tiến hành thực thi shell và truyền tệp cho nó để xử lý.

Nói tóm lại, không có vấn đề gì khi bạn gọi script với shell nào - kernel sẽ gửi lệnh thực thi đến một trong hai cách thích hợp.


4
Có một sự khác biệt rõ rệt giữa bash ./myscript.sh./myscript.sh.
một CVn

Bạn có ý nghĩa gì bởi "sự khác biệt được đánh dấu" này?
jrara

3
@jrara Xem câu trả lời của tôi, tuyên bố rằng "không có vấn đề gì khi bạn gọi kịch bản với" đơn giản là không đúng.
Thomas Nyman
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.