如何复用外部shell脚本


在Linux开发中,经常会编写shell脚本来执行一些任务,通常是一个脚本只做一件事,随着任务的增加,脚本会越来越多,可复用的地方也会逐渐增加,这时就需要提取出脚本中的公共的功能放到一个通用的脚本中,其他脚本都能复用它

本篇文章介绍shell脚本中如何执行外部脚本,如何调用外部脚本中的函数,以及脚本复用相关的方法

<font color=CornflowerBlue>执行外部脚本的方式</font>

假如在当前目录有 a.sh 脚本,内容如下

#!/bin/bash

echo "a.sh..."

在一个脚本中执行外部脚本主要有以下几种方式

  • <font color=CornflowerBlue>source 外部脚本名字</font>

在当前目录下的 b.sh 脚本,内容如下:

#!/bin/bash

source a.sh
echo "b.sh..."

执行 ./b.sh,结果如下

[root@ecs-centos-7 ~]# ./b.sh 
a.sh...
b.sh...

脚本中 source a.sh 命令 会先执行当前目录下的 a.sh脚本,所以结果会先输出 a.sh...再输出 b.sh脚本本身的打印

  • <font color=CornflowerBlue>点号 外部脚本名字</font>

b.sh 脚本中执行a.sh脚本的语句修改成 点号 + 空格 + a.sh ,修改之后的脚本内容如下:

注意:点号和a.sh之间一定要加上空格,否则执行的时候会出错

#!/bin/bash

. a.sh
echo "b.sh..."

执行 ./b.sh,结果如下

[root@ecs-centos-7 ~]# ./b.sh 
a.sh...
b.sh...

在上述脚本中, . a.sh 会先执行a.sh脚本, 结果会先输出 a.sh...再输出 b.sh...

  • <font color=CornflowerBlue>sh 外部脚本名字</font>

sh 外部脚本名字./外部脚本名字 两种方式是一样的,选择哪一种方式都没问题,下面是以前面一种方式为例说明的

b.sh 脚本中 source a.sh修改成 sh a.sh ,修改之后的脚本内容如下:

#!/bin/bash

sh a.sh
echo "b.sh..."

执行 ./b.sh 命令, 结果如下

[root@ecs-centos-7 ~]# ./b.sh 
a.sh...
b.sh...

可以看出,结果输出和上面两种方式是一样的

<font color=CornflowerBlue>三种方式的有什么区别</font>

调用外部脚本有 source 外部脚本点号 外部脚本sh 外部脚本 三种方式,它们之间有什么区别呢?

其中,source 外部脚本点号 外部脚本 两种方式是相同的,当前脚本继承了外部脚本的全局变量和函数, 相当于把外部脚本的函数和全局变量导入了当前脚本中

修改 a.shb.sh 脚本, 内容如下

a.sh脚本

#!/bin/bash

VAR_A=10

func_a()
{
  echo "a.sh...pid:$$,param:$1"
}

b.sh脚本

#!/bin/bash

source a.sh 

func_a $1
echo "vara:$VAR_A"
echo "b.sh...pid:$$"

执行 ./b.sh 5 命令,结果如下

[root@ecs-centos-7 ~]# ./b.sh 5
a.sh...pid:21485,param:5
vara:10
b.sh...pid:21485

两个脚本中的 $$ 是指执行脚本的进程ID,从结果可以看出,a.shb.sh 都是在同一个进程内执行的,所以在 b.sh 脚本中执行 source a.sh 命令,会把 a.sh 脚本中的全局变量 VAR_A 和函数 func_a导入到 b.sh

b.sh中打印变量 VAR_A,输出的值和 a.sh中相同,调用 func_a函数,输出也说明了调用的是 a.sh中的函数

source 外部脚本点号 外部脚本 两种方式是相同的, 所以, 把 b.shsource a.sh 修改成 . a.sh , 执行 ./b.sh 5, 结果依然是相同的

由于 sh 外部脚本的方式是当前脚本和外部脚本在两个不同的进程中执行,所以当前脚本不能直接使用外部脚本中的函数和全局变量

修改 a.shb.sh 脚本, 内容如下

a.sh脚本

#!/bin/bash

test_a()
{
  echo "a.sh...test_a"
}

echo "a.sh...pid:$$"

b.sh脚本

#!/bin/bash

sh a.sh

echo "b.sh...pid:$$"

test_a

执行 ./b.sh 命令,结果如下

[root@ecs-centos-7 ~]# ./b.sh 
a.sh...pid:21818
b.sh...pid:21817
./b.sh:行7: test_a: 未找到命令

从结果可以看出,执行 a.shb.sh 的进程ID是不同的,b.sh脚本进程找不到test_a函数,所以在b.sh中调用test_a 函数会提示 未找到命令

<font color=CornflowerBlue>调用外部脚本中的函数</font>

上一节讲到 sh 外部脚本 的方式无法直接使用外部脚本中函数和全局变量,下面提供几种方法可以解决这个问题

  • <font color=CornflowerBlue>case 分支选择</font>

这种方法类似于程序代码中的 switch case 语句,通过switch 选择不同的分支从而执行不同的逻辑,shell脚本中是使用case关键字来实现的

a.sh脚本

#!/bin/bash

VAR_A=10

test_a()
{
   echo "test_a..pid:$$,p1:$1,p2:$2"
}
get_var()
{
  echo ${VAR_A}
}

case "$1" in
    ta)
      test_a $2 $3
      ;;
    var)
      get_var
      ;;
   *)
      echo "parameter err..."
esac

b.sh脚本

#!/bin/bash

echo "b.sh...pid:$$"

sh a.sh ta 3 5

ret=$(sh a.sh var)

echo "ret:$ret"

执行 ./b.sh 命令,结果如下

[root@ecs-centos-7 ~]# ./b.sh 
b.sh...pid:24813
test_a..pid:24814,p1:3,p2:5
ret:10

脚本b.sh一开始打印了调用自身的进程ID

sh a.sh ta 3 5 语句是调用a.sh脚本,传入的三个参数分别是ta, 3, 5 ,执行a.sh时,传入的第一个参数 ta 经过case匹配之后调用 test_a函数,并把剩下的两个参数 35作为参数传入函数

ret=$(sh a.sh var) 语句时调用a.sh脚本,传入一个var 参数,经过case匹配之后调用get_var函数,该函数的作用输出脚本中全局变量VAR_A的值,语句中$()的作用是获取()中命令的返回值,这里是把a.sh脚本中 get_var函数的返回值赋值给 ret变量,所以该变量的值是 a.sh脚本中全局变量VAR_A的值

说明:如果想要获取函数的返回值,可以在函数中用 echo 打印相应的输出值,然后使用$(函数名 参数列表)可以获取到函数中打印的值,如上面b.sh脚本中 ret=$(sh a.sh var)语句,变量ret的值是 a.sh脚本中 get_var函数输出的值10

这里需要注意的是, 如果函数中有echo调试日志,那么调试日志也会一起返回

  • <font color=CornflowerBlue>函数调用模板</font>

上面介绍的用 case 关键字去匹配调用不同的函数有一个缺点,每次a.sh脚本中增加一个函数的时候,case 就需要添加一个分支,分支里调用不同的函数,还需要注意函数是否有参数传入以及参数数量是否正确

我们可以在每个供外部调用脚本的尾部加上以下的语句,就可以解决上述问题, 具体语句如下

if [ $# -ge 1 ]; then
   name="$1"
   shift 1
   $name "$@"
fi

上述语句首先判断调用脚本时传入的参数数量,只有参数数量大于等于1才有效,传入的第一个参数表示函数名字,从第二个参数到最后一个参数都会作为参数传入到函数中

这里的 shift 1 是把传入脚本的参数左移一个位置,比如:传入脚本参数有 $1 $2 $3三个参数,左移一个位置之后, $2 移动到 $1 的位置,$3 移动到 $2 的位置,参数数量变为2了

原因: 传入脚本的参数中,第一个参数是函数名字,从第二个参数起才是函数的参数,如果不做左移处理,第一个参数函数名字也会作为参数传入到函数中

下面是完整的脚本内容

a.sh脚本

#!/bin/bash

VAR_A=10

test_a()
{
   echo "test_a..pid:$$,p1:$1,p2:$2"
}

get_var()
{
  echo ${VAR_A}
}

if [ $# -ge 1 ]; then
   name="$1"
   shift 1
   $name "$@"
fi

b.sh脚本

#!/bin/bash

echo "b.sh...pid:$$"

sh a.sh test_a 3 5

ret=$(sh a.sh get_var)

执行 ./b.sh 命令,结果如下

[root@ecs-centos-7 ~]# ./b.sh 
b.sh...pid:25086
test_a..pid:25087,p1:3,p2:5
ret:10

可以看出,结果和上面 case 的方法是一样的

现在其他脚本中都可以通过 sh a.sh 函数名 参数列表 这样的方式调用 a.sh 脚本中的函数了,通过 $(sh a.sh 函数名 参数列表)的方式获取 a.sh脚本函数的返回值

  • <font color=CornflowerBlue>两者的优缺点</font>

与case分支选择的方式相比,函数调用模板的优点是调用者只需要关心复用的脚本中函数名、函数传入参数、函数返回值就可以直接使用

缺点是如果有多个脚本都调用了复用脚本中的函数,当复用脚本中函数名变更时,需要修改所有调用了它的地方

函数调用模板方式的缺点恰恰是case分支选择方式的有点,case分支选择的方式时根据传入的字符串参数调用不同的函数,这里的字符串参数相当于函数的别名,只要这个参数保持不变,脚本中的函数名字可以任意变更

上述的优缺点比较只是一个相对的比较,实际应用中下不会很明显,大部分情况两种方式都可以使用

<font color=CornflowerBlue>小结</font>

在编写shell脚本的过程中,经常会遇到一些莫名奇妙的问题,有些问题就算挠破头皮都不知道如何解决,脚本复用可以把一些公共功能提取出来,形成一个个的功能模块,不仅有助于减少我们编写脚本时犯的错误,而且对后期的脚本维护很有帮助

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容