1. 程式人生 > >bash執行命令各種情況分析

bash執行命令各種情況分析

Linux系統中的可執行檔案有多少種類?bash環境下是如何執行程式的?下面逐一分析。

1 Linux系統中可執行檔案種類

1.1 二進位制可執行檔案

這種檔案是最常見的,如/bin/ls,/sbin/ifconfig, /bin/cat等等。

[[email protected] ~]# file /bin/ls /bin/cat /sbin/ifconfig

/bin/ls:        ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped

/bin/cat:       ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped

/sbin/ifconfig: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped

1.2 可執行指令碼檔案

這種檔案是系統管理者常用的,通常用來粘合各種其他的程式,系統中也必不可少,如 命令service,yum等。

[[email protected] ~]# file $(which yum) $(which service)

/usr/bin/yum:  a /usr/bin/python script text executable

/sbin/service: POSIX shell script text executable

其中,yum是一個python指令碼,service是一個shell指令碼。

當然,還可以有其他型別的指令碼,如ruby,php,awk等等。

1.3 系統載入器對可執行檔案的識別驗證與載入

不論何種型別的可執行程式,都是通過統一的execve()系統呼叫進行載入執行的。具體的載入過程卻會因為可執行程式種類的不同而不同,對於二進位制檔案,直接載入執行;對於指令碼檔案,則會載入檔案第一行指定的直譯器,並把指令碼檔案路徑名作為直譯器的引數。execve()是如何區分各種程式型別的呢?其實很簡單,就是位於檔案最開始處的“魔數”。不同型別的檔案的“魔數”是不同的,對於指令碼檔案就是開頭的#!,對於二進位制檔案,根據32位和64位的不同,也有所不同,感興趣的同學可以自行研究,這裡不再累述。

2 bash執行命令的過程

2.1 bash執行內建命令的過程

bash環境下可以執行的命令有兩類:一類就是前面說過的可執行檔案(可執行檔案又分成二進位制和指令碼兩類);另一類就是bash內建命令。

常見的內建命令有echo,cd,trap等等。內建命令也分成兩類:一類是某些外部可執行檔案的同功能替代,目的是提高效率,如echo;第二類是其功能無法通過執行外部檔案完成的必備命令,如cd。

對於內部命令,bash直接執行其自身內部程式碼即可,快速無負擔。對於執行外部可執行檔案,則相對就麻煩了。

2.2 bash執行外部二進位制可執行檔案五種方式

(1)直接執行

可以有兩種手段讓bash執行命令,一是在互動模式下,輸入命令名然後按下回車鍵;二是把命令路徑作為引數來執行/bin/bash。兩種手段的作用是相同的。

bash首先會fork出一個子程序,然後:(1)bash自身程序執行wait()等待子程序結束;(2)子程序中執行execve("命令路徑“),剩下的工作就由載入器來完成了。

[[email protected] ~]# strace -e trace=process /bin/ls

execve("/bin/ls", ["/bin/ls"], [/* 21 vars */]) = 0

(2)使用exec命令執行(不建立子程序)

smstongtekiMac-mini:Test smstong$ exec /bin/ls 


此時,可執行程式將會在程序內把原來的bash程式替換掉。相當於直接執行execve("可執行程式")。

2.3 執行指令碼檔案的三種方式

(1)直接執行

前面我們說過,execve()可以自動識別指令碼檔案型別,並自動載入相應的直譯器。如下:

[[email protected] ~]# strace -e trace=process ./test1.sh 

execve("./test1.sh", ["./test1.sh"], [/* 21 vars */]) = 0


(2)人工指定直譯器執行

當然,這個識別指令碼的過程也可以交給使用者自己來完成,比如對於上面的 test1.sh,我們可以指定ksh93為其直譯器,者通過顯式的執行其直譯器程式來完成。

[[email protected] ~]# strace -e trace=process ksh93 ./test1.sh 

execve("/bin/ksh93", ["ksh93", "./test1.sh"], [/* 21 vars */]) = 0

此時,指令碼檔案第一行指定的直譯器不起作用了。

這兩種執行指令碼檔案的方式效果是一樣的,只是通過ps檢視時程序的名字會有所不同,直接執行時子程序名字為指令碼檔名,通過ksh93 test1.sh執行時,子程序名為ksh93。

其實第二種方式有時候是必要的,例如某些系統的載入器本身不能直接執行指令碼檔案,又如指令碼檔案本身不具有可執行許可權。

前面例子test1.sh是ksh指令碼,如果是awk,php,python...,過程也是完全一樣的。有一中指令碼也許看起來有點特殊,那就是bash,此時會導致bash啟動一個子bash程序,其實也就是看起來特殊,僅僅是因為子程序的名字和父程序一樣而已,本質上與其他型別的指令碼直譯器沒有任何區別。

(3)使用 . 命令執行指令碼(不生成子程序)

bash還提供了一種特殊的執行外部命令的方式,那就是使用自身程序去執行,而不是新建子程序。這時,指令碼檔案第一行指定的直譯器不起作用了。 這種方式的優點是無需建立子程序,當然效率高。其缺點也是明顯的,因為失去了程序的隔離,所以指令碼檔案會直接破壞bash程序,安全性差。例如如下指令碼: vim test1.sh

  1 #!/bin/ksh93

  2 echo$$

  3 exit

如果直接執行或者通過bash ./test1.sh執行,結果都是列印子程序號,然後退出子程序,回到互動式bash。 而如果通過. ./test.sh執行,則列印bash程序號後,bash自身退出了。 此種方式的主要作用是通過指令碼設定互動式bash自身的環境,如/etc/profile檔案就需要此種方式執行。 需要說明的是,此種方式不同於 exec ./test1.sh。

(4) 使用eval命令執行(不產生子程序)

eval與(3)的點命令原理一樣,但是提供了更多的引數。

(5) 使用exec 命令執行指令碼檔案(不產生子程序)

exec 內建命令可以作用於二進位制,也可以作用於可執行的指令碼檔案,前面已經說過了。