Trình thông dịch shell nào chạy một script không có shebang?


16

Giả sử shell mặc định cho tài khoản của tôi là zsh nhưng tôi đã mở terminal và khởi động bash và thực thi một script có tên prac002.sh, trình thông dịch shell nào sẽ được sử dụng để thực thi script, zsh hay bash? Hãy xem xét ví dụ sau:

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % sudo cat /etc/passwd | grep papagolf
[sudo] password for papagolf: 
papagolf:x:1000:1001:Rex,,,:/home/papagolf:/usr/bin/zsh
# papagolf's default shell is zsh

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % bash
# I fired up bash. (See that '%' prompt in zsh changes to '$' prompt, indicating bash.)

papagolf@Sierra:~/My Files/My Programs/Learning/Shell$ ./prac002.sh 
Enter username : Rex
Rex
# Which interpreter did it just use?

** EDIT: ** Đây là nội dung của kịch bản

papagolf@Sierra ~/My Files/My Programs/Learning/Shell % cat ./prac002.sh 
read -p "Enter username : " uname
echo $uname

Kịch bản nói gì ở đầu?
Michael Homer

@MichaelHomer: Không có gì. Tôi chỉ chỉnh sửa câu hỏi để thêm nội dung của kịch bản. Kịch bản có sự cho phép thực thi.
7_R3X

Nếu bạn muốn sử dụng shell hiện tại, hãy chấm nguồn nó, như trong . prac002.sh, giả sử tập lệnh của bạn nằm trong thư mục hiện tại.
thecarpy

1
Chạy . ./prac002.shvà nó sẽ chạy với shell hiện tại, nghĩa là dot ( .), space () theo sau là đường dẫn của tập lệnh của bạn. Nó được gọi là chấm nguồn tìm kịch bản của bạn. ;-)
thecarpy

Câu trả lời:


18

Vì tập lệnh không bắt đầu bằng #!dòng shebang cho biết nên sử dụng trình thông dịch nào, POSIX nói rằng :

Nếu execl()chức năng không thành công do lỗi tương đương với lỗi [ENOEXEC] được xác định trong khối Giao diện hệ thống của POSIX.1-2008, trình bao sẽ thực thi một lệnh tương đương với trình bao được gọi với tên đường dẫn từ tìm kiếm như là lần đầu tiên toán hạng , với bất kỳ đối số còn lại nào được chuyển sang shell mới, ngoại trừ giá trị "$ 0" trong shell mới có thể được đặt thành tên lệnh. Nếu tệp thực thi không phải là tệp văn bản, trình bao có thể bỏ qua lệnh thực thi này. Trong trường hợp này, nó sẽ viết một thông báo lỗi và sẽ trả về trạng thái thoát là 126.

Phrasing đó là một chút mơ hồ, và vỏ khác nhau có cách hiểu khác nhau.

Trong trường hợp này, Bash sẽ chạy tập lệnh bằng chính nó . Mặt khác, nếu bạn chạy nó từ zsh thay vào đó, zsh sẽ sử dụngsh (bất cứ thứ gì có trong hệ thống của bạn) thay vào đó.

Bạn có thể xác minh hành vi đó cho trường hợp này bằng cách thêm các dòng này vào tập lệnh:

echo $BASH_VERSION
echo $ZSH_VERSION

Bạn sẽ lưu ý rằng, từ Bash, dòng đầu tiên xuất ra phiên bản của bạn, trong khi dòng thứ hai không bao giờ nói bất cứ điều gì, bất kể bạn sử dụng shell nào.

  • Nếu bạn /bin/shlà, giả sử, dashthì không dòng nào sẽ xuất bất cứ thứ gì khi tập lệnh được thực thi từ zsh hoặc dash.
  • Nếu bạn /bin/shlà một liên kết đến Bash, bạn sẽ thấy đầu ra dòng đầu tiên trong mọi trường hợp.
  • Nếu /bin/shlà một phiên bản Bash khác với bạn đang sử dụng trực tiếp, bạn sẽ thấy đầu ra khác nhau khi bạn chạy tập lệnh từ bash trực tiếp và từ zsh.

Các ps -p $$lệnh từ câu trả lời của rools cũng sẽ hiển thị thông tin hữu ích về các lệnh shell sử dụng để thực thi kịch bản.


Tôi đã nâng cấp, tuy nhiên, nó sử dụng DEFAULT SHELL nếu không có shebang nào được chỉ định, bất kỳ shell mặc định nào bạn đã chỉ định. Đây là lý do tại sao bạn nên luôn luôn, imho, chỉ định một shebang.
thecarpy

4
Nó không, đó thực sự là toàn bộ điểm của câu trả lời. Câu thứ hai của bạn chắc chắn là đúng.
Michael Homer

bạn đã đúng, bash chạy nó bằng chính nó, vỏ mặc định trên hầu hết các hộp của tôi xảy ra là .... bash, từ đó sự nhầm lẫn của tôi. Tôi vừa thử nghiệm trên Solaris 8 với ksh làm vỏ mặc định, chắc chắn rồi, bash chạy nó như bash ... đã học được điều gì đó mới hôm nay, cảm ơn (nâng cấp!) ;-)
thecarpy

Cảm ơn. Có phải sự thất bại của execl()cuộc gọi xảy ra khi tập lệnh shell không chứa shebang và được thực thi như scriptname? Nó không xảy ra khi một kịch bản shell được thực thi như bash scriptname? Nó không xảy ra khi một tập lệnh shell chứa một shebang và được thực thi như scriptname?
StackExchange cho tất cả


7

Vì tệp không thuộc bất kỳ loại thực thi nào được hệ thống nhận ra và giả sử bạn đã có quyền thực thi tệp đó, nên execve()cuộc gọi hệ thống thường sẽ thất bại với lỗi ENOEXEC( không phải là thực thi ).

Điều gì xảy ra sau đó tùy thuộc vào ứng dụng và / hoặc chức năng thư viện được sử dụng để thực thi lệnh.

Ví dụ, đó có thể là shell, hàm execlp()/ execvp()libc.

Hầu hết các ứng dụng khác sẽ sử dụng một trong những ứng dụng đó khi chúng chạy lệnh. Ví dụ, họ sẽ gọi shell bằng system("command line")hàm libc, thường sẽ gọi shđể phân tích dòng lệnh đó (đường dẫn có thể được xác định tại thời điểm biên dịch (như /bin/shso với /usr/xpg4/bin/shtrên Solaris)) hoặc gọi shell được lưu trữ $SHELLbởi chính họ như vivới !lệnh của nó hoặc xterm -e 'command line'nhiều lệnh khác ( su user -csẽ gọi shell đăng nhập của người dùng thay vì $SHELL).

Nói chung, một tệp văn bản không có shebang không bắt đầu #được coi là một shtập lệnh. Mà shnó sẽ thay đổi mặc dù.

execlp()/ execvp(), khi execve()trở về ENOEXECthường sẽ gọi shnó. Đối với các hệ thống có nhiều hơn một shvì chúng có thể tuân thủ nhiều hơn một tiêu chuẩn, shđiều này thường được xác định tại thời điểm biên dịch (của ứng dụng sử dụng execvp()/ execlp()bằng cách liên kết một blob mã khác nhau có liên quan đến một đường dẫn khác sh). Chẳng hạn, trên Solaris, đó sẽ là /usr/xpg4/bin/sh(một tiêu chuẩn, POSIX sh) hoặc /bin/sh(vỏ Bourne (vỏ cổ) trên Solaris 10 trở lên, ksh93 trong Solaris 11).

Khi nói đến đạn pháo, có rất nhiều biến thể. bash, AT & T ksh, shell Bourne thường sẽ tự giải thích tập lệnh (trong một quy trình con trừ khi execđược sử dụng) sau khi đã mô phỏng a execve(), đó là đặt tất cả các biến không được báo cáo, đóng tất cả các fds đóng-thực thi, loại bỏ tất cả các bẫy tùy chỉnh, bí danh, hàm ... ( bashsẽ diễn giải tập lệnh trong shchế độ). yashsẽ tự thực thi (với chế độ shnhư argv[0]vậy sh) để diễn giải nó.

zsh, pdksh, ashVỏ dựa trên thường sẽ gọi sh(con đường mà xác định tại thời gian biên dịch).

Đối với cshtcsh(và shcủa một số BSD đầu tiên), nếu ký tự đầu tiên của tệp là #, thì họ sẽ tự thực thi để giải thích nó, và shnếu không. Điều đó quay trở lại thời tiền shebang, nơi cshđã nhận ra #là bình luận nhưng không phải là vỏ Bourne, vì vậy đó #là một gợi ý rằng đó là một kịch bản csh.

fish(ít nhất là phiên bản 2.4.0), chỉ trả về lỗi nếu execve()không thành công (không cố xử lý nó như một tập lệnh).

Một số shell (như bashhoặc AT & T ksh) trước tiên sẽ cố gắng xác định một cách chính xác xem liệu tệp có thể có nghĩa là một tập lệnh hay không. Vì vậy, bạn có thể thấy rằng một số shell sẽ từ chối thực thi tập lệnh nếu nó có ký tự NUL trong vài byte đầu tiên.

Cũng lưu ý rằng nếu execve()thất bại với ENOEXEC nhưng tệp có dòng shebang, một số shell cố gắng tự giải thích dòng shebang đó.

Vì vậy, một vài ví dụ:

  • Khi $SHELL/bin/bash, xterm -e 'myscript with args'sẽ có myscriptgiải thích bởi bashtrong shchế độ. Trong khi với xterm -e myscript with args, xtermsẽ sử dụng execvp()để kịch bản sẽ được giải thích bởi sh.
  • su -c myscripttrên Solaris 10, nơi rootđăng nhập của shell /bin/sh/bin/shlà shell Bourne sẽ được myscriptgiải thích bởi shell Bourne.
  • /usr/xpg4/bin/awk 'BEGIN{system("myscript")'trên Solaris 10 sẽ có nó được giải thích bởi /usr/xpg4/bin/sh(tương tự cho /usr/xpg4/bin/env myscript).
  • find . -prune -exec myscript {} \;trên Solaris 10 (sử dụng execvp()) sẽ được giải thích /bin/shngay cả với /usr/xpg4/bin/find, ngay cả trong môi trường POSIX (lỗi tuân thủ).
  • csh -c myscriptsẽ được giải thích bởi cshnếu nó bắt đầu bằng #, shnếu không.

Nói chung, bạn không thể chắc chắn cái vỏ nào sẽ được sử dụng để diễn giải kịch bản đó nếu bạn không biết nó sẽ được gọi như thế nào và bằng cách nào.

Trong mọi trường hợp, read -pbashcú pháp đơn thuần, vì vậy bạn sẽ muốn đảm bảo rằng tập lệnh được diễn giải bởi bash(và tránh .shphần mở rộng gây hiểu lầm đó ). Hoặc bạn biết đường dẫn của bashtệp thực thi và sử dụng:

#! /path/to/bash -
read -p ...

Hoặc bạn có thể thử và dựa vào $PATHtra cứu bashthực thi (giả sử đã bashđược cài đặt) bằng cách sử dụng:

#! /usr/bin/env bash
read -p ...

( envhầu như có mặt khắp nơi trong /usr/bin). Ngoài ra, bạn có thể làm cho nó tương thích POSIX + Bourne trong trường hợp bạn có thể sử dụng /bin/sh. Tất cả các hệ thống sẽ có một /bin/sh. Trên hầu hết chúng sẽ tương thích (đối với hầu hết các phần) tương thích POSIX, nhưng bạn vẫn có thể tìm thấy bây giờ và sau đó là một vỏ Bourne ở đó.

#! /bin/sh -
printf >&2 'Enter a user name: '
read user
printf '%s\n' "$user"

1

Khi bạn không có bất kỳ dòng #!(được gọi là shebang ), sh sẽ được sử dụng. Để kiểm tra điều đó, bạn có thể chạy đoạn script sau.

ps -p $$
echo -n "The real shell is: "
realpath /proc/$$/exe

Trên máy tính của tôi, tôi nhận được

  PID TTY          TIME CMD
13718 pts/16   00:00:00 sh
The real program is: /usr/bin/bash

ngay cả khi shell mặc định của tôi là zsh . Nó sử dụng bash vì trên máy của tôi, lệnh sh được thực hiện bằng bash .


1
Tại sao bạn downvote điều đó? Vui lòng giải thích hoặc điều đó là vô ích ...
ngốc

Kẻ ghét (kẻ hạ bệ ẩn danh thầm lặng) sẽ ghét. Tôi đã học được điều gì đó từ câu trả lời của bạn, vì vậy đây là một upvote. :-)
Mac
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.