|
文件: | src.tar.gz |
大小: | 1KB |
下载: | 下载 |
|
在运行一些用shell写的脚本时,通常会因为缺少某些命令而无法执行。作为程序的使用者,只能想办法根据错误的提示,把一些命令(工具)安装上了。而作
为程序的开发人员,可以采取两方面的措施:一方面是在用到相关外部命令(工具)的地方,如果找不到该命令(工具)时应该进行合理的提示,告诉用户安装相关
工具;另外一方面是在用户使用你写的程序前,提示用户把相关工具安装上,对于前者,程序员可能会引入大量的命令查找和提示代码,这些代码大多重复,写起来
很枯燥乏味的,不过通常情况下还是必须的,对于后者,貌似也很麻烦,在编写代码之前,我们往往不关心这个代码具体用哪些外部命令来实现,而是倾向于使用那
些我们能够很快想到和偏好的命令,因此要想让用户事先安装上该代码依赖的一些外部工具,那么事先得从脚本中把它们找出来。下面,我们就介绍这个有趣的事
情。
在一个脚本文件中,虽然命令的位置往往比较特殊,但总是跟某些特殊字符在一起,程序员能够很容易识别它们,不过,如果代码很大,让人去做这个事情还是很糟
糕的,呵呵。是否可以用正则表达式什么的呢?这个嘛,肯定是可以的,不然shell解释器怎么处理我们的shell程序。但是,即使可以,也确实是太繁杂
了(找个脚本,看看一个命令可能出现的位置你就知道这个工作用正则表达式来做有多“恐怖”了),做这个事情要是都涉及到来实现解释器的一部分,那也太不划
算了。那有什么别的办法呢?
“动态跟踪程序的执行过程,找出该过程中调用的所用命令。”下面来解释一下这个办法为什么可以。
okay,这里我们先来考虑脚本的执行过程:
当一个脚本被执行时,bash为该脚本创建一个子进程,并获取其中的代码,在子进程中执行它,如果遇到了新的命令,那么又会创建新的子进程来执行它们。关于一个程序在命令行执行的更多细节可以参考资料[1]。
这里创建子进程之后把相应的命令通过execve系统调用来执行是我们解决问题的关键。在这个execve调用里头,有一个很关键的参数,那就是命令本
身,即命令的全路径,比如/bin/bash。这样的话,如果能够跟踪到一个脚本程序执行过程中的所有execve调用,那么也就可以找出这个程序依赖的
那些外部命令了。
呵呵,恰好有这么一个工具,叫strace,它本来就是用来跟踪系统调用的,因此也能够用来跟踪execve这个系统调用了。另外,strace还提供了
一个特别的参数-f,来跟踪子进程的系统调用。这样就可以把一个程序执行过程中所有的execve找出来了,进而找出这些系统调用执行的命令,从而解决我
们提出的问题。
下面来做个演示吧,下面以vnstat.sh为例,可以到附件里头下载。
1、找出某个脚本程序运行时所依赖的所有外部命令
$ strace -f ./vnstat.sh 2>&1 | grep execve | awk -F "\"" '{printf("%s\n", $2);}' | egrep -v "^$|vnstat.sh$" | sort -u /bin/date /bin/grep /usr/bin/awk /usr/bin/cut /usr/bin/tr /usr/bin/vnstat
|
上面把所有依赖的命令都找出来了,但是并没有找出shell脚本解释器本身,所以这个需要注意一下。而在程序运行过程中,可能还跟外部命令的版本有关系,因为相同命令不同版本之间的功能可能有很大差异,所以这个版本号最好也要能够告诉用户。
2、打印所依赖命令的版本号
$
strace -f ./vnstat.sh 2>&1 | grep execve | awk -F "\""
'{printf("%s\n", $2);}' | egrep -v "^$|vnstat.sh$" | sort -u | while
read exe; do eval $exe --version | sed -ne "1p"; done date (GNU coreutils) 6.10 GNU grep 2.5.3 GNU Awk 3.1.6 cut (GNU coreutils) 6.10 tr (GNU coreutils) 6.10 vnStat 1.6 by Teemu Toivola
|
需要提到的是,这里采用了"命令名
--version"来打印某个命令的版本号,不过有些命令并不支持这种方式,而仅仅支持-V或者根本就没有对应的打印版本号的选项,所以以上方法并不是
通用的,因此对于那些没法直接通过--version或者-V确定版本信息的命令,你就得自己设法找了。
上面提到了shell脚本解释器并没有通过上面的方式找出来,所以得自己把它找出来。
3、打印shell解释器以及它的版本号。
$ echo $SHELL /bin/bash $ eval $SHELL --version | sed -ne "1p" GNU bash, version 3.2.33(1)-release (i486-pc-linux-gnu)
|
下面整理出一个小小的脚本来做这个事情吧。
Code:
[Ctrl+A Select All]
运行一下看看。
这里跟踪我们之前用的那个演示脚本。
$ ./printdep.sh vnstat.sh 1. the dependent files of vnstat.sh: /bin/bash /bin/date /bin/grep /usr/bin/awk /usr/bin/cut /usr/bin/tr /usr/bin/vnstat 2. the versions of them: GNU bash, version 3.2.33(1)-release (i486-pc-linux-gnu) date (GNU coreutils) 6.10 GNU grep 2.5.3 GNU Awk 3.1.6 cut (GNU coreutils) 6.10 tr (GNU coreutils) 6.10 vnStat 1.6 by Teemu Toivola
这里跟踪它自己,呵呵。
$ ./printdep.sh printdep.sh 1. the dependent files of printdep.sh: /bin/bash /usr/bin/basename 2. the versions of them: GNU bash, version 3.2.33(1)-release (i486-pc-linux-gnu) basename (GNU coreutils) 6.10
|
下面来个小节,回顾一下上面介绍的方法,该方法能够有效的找出脚本程序依赖的外部命令,但是还是会有一些问题的:如果仅仅通过跟踪某个程序的一次执行过程
就把跟踪到的外部命令作为最终的结果,那是不可靠的,因为一个脚本程序可能有很多不同的执行条件,在执行过程中有很多条件分支,一次执行过程只有一条执行
路径,因此可能无法覆盖所有的结果,如果要找出所有的结果,那么得设法遍历该脚本程序的所有可能条件或者是那些最具有代表性的条件。
参考资料
[1] Linux命令行上程序执行的那一刹那
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1543.html
阅读(1565) | 评论(0) | 转发(0) |