|
|
|
|
移动端

2.6.2 Shell脚本的执行

《跟老男孩学Linux运维:Shell编程实战》本书是一本较完整的Shell编程实战型图书,并非大而全,但处处可以体现实战二字,大多内容取于企业实战,并结合老男孩十几年的运维工作和教学工作进行了梳理。本节为大家介绍Shell脚本的执行。

作者:老男孩来源:机械工业出版社|2017-04-20 18:32

有奖调研 | 1TB硬盘等你拿 AI+区块链的发展趋势及应用调研


2.6.2 Shell脚本的执行

当Shell脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件(加载顺序通常是/etc/profile、~/.bash_prof?ile、~/.bashrc、/etc/bashrc等),在加载了上述环境变量文件后,Shell就开始执行Shell脚本中的内容(更多Shell加载环境变量的知识请见第3章)。

Shell脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在Shell脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。

通常情况下,在执行Shell脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子Shell脚本,基本流程如图2-3所示。

特殊技巧: 设置Linux的crond任务时,最好能在定时任务脚本中重新定义系统环境变量,否则,一些系统环境变量将不会被加载,这个问题需要注意!

Shell脚本的执行通常可以采用以下几种方式。

1)bash script-name或sh script-name:这是当脚本文件本身没有可执行权限(即文件权限属性x位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。这也是老男孩推荐使用的方法。

2)path/script-name或./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行(即文件权限属性加x位),具体方法为chmod +x script-name。然后通过脚本绝对路径或相对路径就可以直接执行脚本了。

在企业生产环境中,不少运维人员在写完Shell脚本之后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第1种方法替代第2种。

3)source script-name或. script-name:这种方法通常是使用source或“.”(点号)读入或加载指定的Shell脚本文件(如san.sh),然后,依次执行指定的Shell脚本文件san.sh中的所有语句。这些语句将在当前父Shell脚本father.sh进程中运行(其他几种模式都会启动新的进程执行子脚本)。因此,使用source或“.”可以将san.sh自身脚本中的变量值或函数等的返回值传递到当前父Shell脚本father.sh中使用。这是它和其他几种方法最大的区别,也是值得读者特别注意的地方。

source或“.”命令的功能是:在当前Shell中执行source或“.”加载并执行的相关脚本文件中的命令及语句,而不是产生一个子Shell来执行文件中的命令。注意“.”和后面的脚本名之间要有空格。

如果读者学过PHP开发就会明白,source或“.”相当于include的功能。HTTP服务软件Apache、Nginx等配置文件里都支持这样的用法。

4)sh

范例2-4:创建模拟脚本test.sh,并输入如下内容。

  1. [oldboy@oldboy ~]$ cat >test.sh #<==编辑test.sh脚本文件。  
  2. echo 'I am oldboy' 

输入“echo 'I am oldboy'”内容后按回车键,然后再按Ctrl+d组合键结束编辑。此操作为特殊编辑方法,这里是作为cat用法的扩展知识(通过使用来记忆是个好习惯)。

现在使用第1种方法实践,命令如下:

  1. [oldboy@oldboy ~]$ cat test.sh  
  2. echo 'I am oldboy'  
  3. [oldboy@oldboy ~]$ sh test.sh   #<==使用第1种方式的sh命令执行test.sh脚本文件。  
  4. I am oldboy  
  5. [oldboy@oldboy ~]$ bash test.sh #<==使用第1种方式的bash命令执行test.sh脚本文件。  
  6. I am oldboy 

这里使用第1种方法的bash和sh,均可以执行脚本并得到预期的结果。

使用第2种方法实践,命令如下:

  1. [oldboy@oldboy ~]$ ls -l test.sh  
  2. -rw-rw-r-- 1 oldboy oldboy 19 Apr 30 02:46 test.sh  
  3. [oldboy@oldboy ~]$ ./test.sh    #<==使用第2种方式“./”在当前目录下执行test.sh脚本文件,细心的读者可以发现,这个地方无法自动补全,这是因为没有权限所导致的。  
  4. -bash: ./test.sh: Permission denied #<==提示:强制执行会提示权限拒绝,此处是  
  5.                          因为没有执行权限。 

虽然没有权限的test.sh脚本不能直接被执行,但是可以用source或“.”(点号)来执行,如下:

  1. [oldboy@oldboy ~]$ . test.sh        #<==请注意开头的 “.”后面有空格。  
  2. I am oldboy  
  3. [oldboy@oldboy ~]$ source test.sh  
  4. I am oldboy 

提示: “.”或source命令的功能相同,都是读入脚本并执行脚本。

给test.sh添加可执行权限,命令如下:

  1. [oldboy@oldboy ~]$ chmod u+x test.sh  
  2. [oldboy@oldboy ~]$ ./test.sh  
  3. I am oldboy 

可以看到,给test.sh加完可执行权限后就能执行了。前面也提到了,这种方法在使用前每次都需要给定执行权限,但容易被忘记,且多了一些步骤,增加了复杂性。

使用第3种方法实践时,会将source或“.”执行的脚本中的变量值传递到当前的Shell中,如下:

  1. [oldboy@oldboy ~]$ echo 'userdir=`pwd`' >testsource.sh #<==第一行的内容通常用  
  2.  echo处理更方便  
  3. [oldboy@oldboy ~]$ cat testsource.sh  
  4. userdir=`pwd`  #<==定义了一个命令变量,内容是打印当前路径。注意,打印命令用反引号  
  5. [oldboy@oldboy ~]$ sh testsource.sh #<==采用sh命令执行脚本  
  6. [oldboy@oldboy ~]$ echo $userdir  
  7.                    #<==此处为空,并没有出现当前路径/home/oldboy的输出,这是为什么 

根据上面的例子可以发现,通过sh或bash命令执行过的脚本,若在脚本结束之后,在当前Shell窗口中查看userdir变量的值,会发现值是空的。现在以同样的步骤改用source或“.”执行,然后再看看userdir变量的值:

  1. [oldboy@oldboy ~]$ source testsource.sh  #<==采用source执行同一脚本  
  2. [oldboy@oldboy ~]$ echo $userdir  
  3. /home/oldboy #<==此处输出了当前路径/home/oldboy,这又是为什么呢 

来了解一下系统NFS服务的脚本是如何使用“.”的:

  1. # Source function library.  
  2. . /etc/init.d/functions          #<==通过"."加载系统函数库functions 

说明: 操作系统及服务自带的脚本是我们学习的标杆和参考(虽然有时感觉这些脚本也不是十分规范)。

结论:通过source或“.”加载执行过的脚本,由于是在当前Shell中执行脚本,因此在脚本结束之后,脚本中的变量(包括函数)值在当前Shell中依然存在,而sh和bash执行脚本都会启动新的子Shell执行,执行完后退回到父Shell。因此,变量(包括函数)值等无法保留。在进行Shell脚本开发时,如果脚本中有引用或执行其他脚本的内容或配置文件的需求时,最好用“.”或source先加载该脚本或配置文件,处理完之后,再将它们加载到脚本的下面,就可以调用source加载的脚本及配置文件中的变量及函数等内容了。

以下采用第4种方法来实践:

  1. [root@oldboy ~]# ls -l oldboy.sh  
  2. -rw-r--r--. 1 root root 28 Nov 18 15:52 oldboy.sh  
  3. [root@oldboy ~]# cat oldboy.sh  
  4. echo "I am oldboy teacher."  
  5. [root@oldboy ~]# sh<oldboy.sh       #<==尽量不要使用这种方法。  
  6. I am oldboy teacher.  
  7. [root@oldboy ~]# cat oldboy.sh|bash #<==这种方法在命令行拼接字符串命令后,需  
  8.                             ?要执行时就会用到  
  9. I am oldboy teacher. 

提示: 代码中提到的两种执行方法相当于sh scripts-name,效率很高,但是初学者用得少。

范例2-5:已知如下命令及返回结果,请问echo $user的返回的结果为()。

  1. [oldboy@test ~]$ cat test.sh  
  2. user=`whoami`  
  3. [oldboy@test ~]$ sh test.sh  
  4. [oldboy@test ~]$ echo $user 

参考的选择项如下:

a)当前用户

b)oldboy

c)空(无内容输出)

这是某互联网公司Linux运维职位的笔试题。在这里c)是正确答案,原因前面已经讲过了,即使用sh执行脚本会导致当前Shell无法获得变量值。

通过上述面试题可得出如下的结论:

儿子Shell脚本会直接继承父亲Shell脚本的变量、函数(就好像是儿子随父亲姓,基因也会继承父亲的)等,反之则不可以。

如果希望反过来继承(就好像是让父亲随儿子姓,让父亲的基因也继承儿子的),就要用source或“.”在父亲Shell脚本中事先加载儿子Shell脚本。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:342347198

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

游戏开发核心技术--剧本和角色创造

《游戏开发核心技术--剧本和角色创造》分“剧本”、“角色”和“游戏玩法”三部分,第一部分着重说明故事的历史、一般故事元素、传统故事设...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊