Linux中的二進制可執行文件和腳本可執行文件及Shebang
二進制可執行文件
我們知道,一個C程序經過預處理、編譯、匯編、鏈接就會得到一個二進制可執行文件,這種文件在Linux中叫做ELF文件。比如我們有一個C源代碼hello.c
:
#include <stdio.h>int main(int argc, char** argv){printf("Hello !\n");
}
我們編譯得到 hello
文件,并用file
命令可以查看到生成的二進制可執行文件的信息:
gcc hello.c -o hello
file hello
# 輸出:
# hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=cf2738fd1715f096d4b0e0e4b264146b78b454b1, not strippe
確實是ELF文件,我們可以直接執行它:
./hello
# 輸出:
# Hello
這是我們常見的,可以理解的,鏈接后的可執行文件就是可以直接運行,就像我們在Windows上雙擊打開一個exe文件那樣自然。那么,腳本可執行文件又是怎么一回事呢?
腳本可執行文件及Shebang
腳本可執行文件也可以像運行二進制可執行文件那樣來直接運行它。我們知道shell、python等屬于腳本語言。令我們好奇的是,腳本程序如 train.py
等看上去只是一個文本文件,為什么也能直接被執行呢 ?
我們知道,想要運行一個腳本文件,我們需要指定一個解釋器。通常,我們有兩種方式來指定腳本文件的解釋器:
- 在命令行中指定,如
bash run.sh
,python train.py
等。 - 通過文件中的第一行Shebang指定。在腳本文件的頭上,通常會有一行Shebang:
#!
。比如:#!/bin/bash
,#!/home/song/bin/python
等。
Shebang通常出現在類Unix系統的腳本中第一行,作為前兩個字符。在Shebang之后,可以有一個或數個空白字符,后接解釋器的絕對路徑,用于指明執行這個腳本文件的解釋器。在直接調用腳本時,系統的程序載入器會分析 Shebang 后的內容,將這些內容作為解釋器指令,并調用該指令,將載有 Shebang 的文件路徑作為該解釋器的參數,執行腳本,從而使得腳本文件的調用方式與普通的可執行文件類似。例如,以指令#!/bin/sh
開頭的文件,在執行時會實際調用 /bin/sh
程序(通常是 Bourne shell 或兼容的 shell,例如 bash、dash 等)來執行。
由于 # 符號在許多腳本語言中都是注釋標識符,這既是偶然,也是必然。Shebang 的內容會被這些腳本解釋器自動忽略。 在 # 字符不是注釋標識符的語言中,例如 Scheme,解釋器也可能忽略以 #! 開頭的首行內容,以提供與 Shebang 的兼容性。
實際上,#!
兩個字符的ASCII碼是兩個magic字符,當類UNIX操作系統看到一個文件以這兩個字符開頭,會將這個文件當做是可執行文件,并且按照其后的解釋器來執行它(需要有執行權限)。這時,操作系統實際上加載的是 #!
后面跟的那個二進制文件(即解釋器),然后將腳本文件的文本內容作為參數傳給這個二進制文件。這一點可以通過觀察腳本可執行文件運行使得strace結果中的execve
來驗證。
Shebang的一些具體用法和注意事項:
- 如果腳本文件中沒有#!這一行,那么執行時會默認采用當前Shell去解釋這個腳本(即:SHELL環境變量)。
- 如果#!之后的解釋程序是一個可執行文件,那么執行這個腳本時,它就會把文件名及其參數一起作為參數傳給那個解釋程序去執行。
- 如果#!指定的解釋程序沒有可執行權限,則會報錯
bad interpreter: Permission denied
。如果#!指定的解釋程序不是一個可執行文件,那么指定的解釋程序會被忽略,轉而交給當前的SHELL去執行這個腳本。 - 如果#!指定的解釋程序不存在,那么會報錯
bad interpreter: No such file or directory
。注意:#!之后的解釋程序,需要寫其絕對路徑(如:#!/bin/bash
),它是不會自動到環境變量PATH
中尋找解釋器的。要用絕對路徑是因為它會調用系統調用execve
,這可以用strace工具來查看。 - 腳本文件必須擁有可執行權限。可通過
chmod +x [filename]
來添加可執行權限。 - 當然,如果你使用類似于
bash test.sh
,python train.py
這樣的命令來執行腳本,那么#!這一行將會被忽略掉,解釋器當然是用命令行中顯式指定的解釋器。
我們來試一下,先創建一個py文件world.py
,并直接寫入為:
print('World !')
我們可以通過在命令行指定python解釋器來運行,就像我們一直做的那樣:
python world.py
# 輸出:
# World !
但是,當我們想像運行二進制可執行文件那樣來運行它:
./world.py
# 輸出:
# -bash: ./world.py: Permission denied
首先會受到一條沒有執行權限的命令,如上。這很正常,因為我們創建的時候它是一個文本文件嘛。我們通過 chmod
來使得它可執行,并再次嘗試運行它:
chmod +x world.py
./world.py
# 輸出:
# ./world.py: line 1: syntax error near unexpected token `'World !''
# ./world.py: line 1: `print('World !')'
問題出現了,和我們之前討論的一樣,由于我們沒有通過Shebang來指定腳本的解釋器,系統默認用了Shell來解釋,那我們的python語法自然是不對的。那這時,想要像運行二進制可執行文件那樣去運行它,必須請出我們的Shebang來幫忙在文件內指明解釋器的絕對路徑。更改world.py
為:
#!/home/song/anaconda3/envs/JJ_env/bin/python
print("World")
這時我們再來運行:
./world.py
# 輸出:
# World !
就可以了。我們還可以通過 file
命令再來看一下 world.py
的文件信息:
file world.py
# 輸出:
# world.py: a /home/song/anaconda3/envs/JJ_env/bin/python script, ASCII text executable
我們看到該文件是一個ASCII text executable
,即 ”文本可執行文件“。不同于ELF二進制可執行文件,但也是可執行文件。也就是說,在Linux的世界中,可執行文件不只有ELF一種。
另外,由于Linux系統對后綴名并不嚴格要求,我們可以直接將world.py
改為world
,這樣也是可以的,然后就可以通過將world
這個腳本可執行文件放到PATH
環境變量下,從而將world
直接作為一個命令來使用啦!具體可參考筆者另一篇介紹Linux常用環境變量的博客。
總結
總結一下:Linux中除了ELF二進制可執行文件之外,還有腳本可執行文件,要想讓腳本可執行文件直接像二進制可執行文件一樣運行,而不需在命令行中指定解釋器,需要在腳本文件頭通過Shebang !#
來指定解釋器的絕對路徑。Shebang的一些具體的注意事項在上文中已經指出。另外,通過將可執行文件(二進制、腳本都可)添加到PATH
環境變量的可執行文件搜索目錄下,可將在命令行中通過命令來直接使用這些可執行文件。
Ref:
https://blog.csdn.net/u012294618/article/details/78427864