bash执行命令各种情况分析

Linux系统中的可执行文件有多少种类?bash环境下是如何执行程序的?下面逐一分析。

1 Linux系统中可执行文件种类

1.1 二进制可执行文件

这种文件是最常见的,如/bin/ls,/sbin/ifconfig, /bin/cat等等。

[root@notebook135 ~]# 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等。


[root@notebook135 ~]# 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("命令路径“),剩下的工作就由加载器来完成了。

[root@notebook135 ~]# 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()可以自动识别脚本文件类型,并自动加载相应的解释器。如下:

[root@notebook135 ~]# strace -e trace=process ./test1.sh 

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


(2)人工指定解释器执行

当然,这个识别脚本的过程也可以交给用户自己来完成,比如对于上面的 test1.sh,我们可以指定ksh93为其解释器,者通过显式的执行其解释器程序来完成。

[root@notebook135 ~]# 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 内置命令可以作用于二进制,也可以作用于可执行的脚本文件,前面已经说过了。



bash执行命令各种情况分析

上一篇:[开心IT面试题] 查找最小的K个元素(数组)


下一篇:AChartEngine应用之BarChart(柱形图)