你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

shell脚本中如何执行外部脚本

[复制链接]
gaosmile 发布时间:2020-11-14 14:25
在Linux开发中,经常会编写shell脚本来执行一些任务,通常是一个脚本只做一件事,随着任务的增加,脚本会越来越多,可复用的地方也会逐渐增加,这时就需要提取出脚本中的公共的功能放到一个通用的脚本中,其他脚本都能复用它
本篇文章介绍shell脚本中如何执行外部脚本,如何调用外部脚本中的函数,以及脚本复用相关的方法
执行外部脚本的方式
假如在当前目录有 a.sh 脚本,内容如下
#!/bin/bash
1 h- X" N+ ]5 G! ]: n! u" b  M
3 t+ R, C  ^" B- J( d
echo "a.sh..."* k" W4 p+ o/ c  Q- k
在一个脚本中执行外部脚本主要有以下几种方式
  • source 外部脚本名字
    4 F% F$ e' c9 I# `0 a
在当前目录下的 b.sh 脚本,内容如下:
#!/bin/bash' Z" P7 C8 n( [, h! e
9 r/ H+ i9 }* X- _% [7 i) h
source a.sh# P. e4 d2 f  Z" h
echo "b.sh..."
# u: x# T3 H' |1 x/ i5 _5 Q) m
执行 ./b.sh,结果如下
[root@ecs-centos-7 ~]# ./b.sh
4 l, M- {8 f- A- Ba.sh...
* x. B7 V6 F0 _: d2 [. R, d" @) h$ qb.sh...4 e3 {0 {4 F5 ^% g
脚本中 source a.sh 命令 会先执行当前目录下的 a.sh脚本,所以结果会先输出 a.sh...再输出 b.sh脚本本身的打印
  • 点号 外部脚本名字
      k9 h; E; W  }" R( \& U+ J; ], {
把 b.sh 脚本中执行a.sh脚本的语句修改成 点号 + 空格 + a.sh ,修改之后的脚本内容如下:
注意:点号和a.sh之间一定要加上空格,否则执行的时候会出错
#!/bin/bash
1 u1 l) {+ y) _, A# {$ V* ~& v
" }1 r6 z* B% \3 j0 Z
. a.sh* p6 P2 \8 o# |+ T" v
echo "b.sh..."+ P5 B) T* h+ l7 H) t
执行 ./b.sh,结果如下
[root@ecs-centos-7 ~]# ./b.sh
/ P/ B( h" M5 @7 Fa.sh...( r' K9 O% N% _( v- Q$ S
b.sh...
( W' C! w" p+ Q3 r; }% V
在上述脚本中, . a.sh 会先执行a.sh脚本, 结果会先输出 a.sh...再输出 b.sh...
  • sh 外部脚本名字

    . n/ ?9 T/ P9 X8 B3 n% O% J
sh 外部脚本名字 和 ./外部脚本名字 两种方式是一样的,选择哪一种方式都没问题,下面是以前面一种方式为例说明的
把 b.sh 脚本中 source a.sh修改成 sh a.sh ,修改之后的脚本内容如下:
#!/bin/bash! m& D. |, a& L4 n
8 s( M0 m& t0 X; I$ D1 @
sh a.sh
; @1 B9 S2 R: ]echo "b.sh..."
6 z0 p4 r3 I# L7 y& w
执行 ./b.sh 命令, 结果如下
[root@ecs-centos-7 ~]# ./b.sh " e& ~- Z3 u! c) ^+ d
a.sh...
0 Z2 t8 Z* z1 |b.sh...
& V5 q6 X* v+ \* s. K5 F
可以看出,结果输出和上面两种方式是一样的
三种方式的有什么区别
调用外部脚本有 source 外部脚本 、点号 外部脚本、sh 外部脚本 三种方式,它们之间有什么区别呢?
其中,source 外部脚本 和 点号 外部脚本 两种方式是相同的,当前脚本继承了外部脚本的全局变量和函数, 相当于把外部脚本的函数和全局变量导入了当前脚本中
修改 a.sh 和 b.sh 脚本, 内容如下
a.sh脚本
#!/bin/bash% y" g* Z; ]0 G; P, i

/ c' n/ H) w8 I$ S" m; yVAR_A=103 ~# q: H& U1 ~, Q! S" t

3 ~# i/ R+ t- dfunc_a()
3 A) Z' S9 y. A1 Z& V{8 K& j4 g3 x' A2 q1 F9 {3 g
  echo "a.sh...pid$,param1"1 |, E, q" y3 w6 h2 G
}( \8 p% l: r1 d
b.sh脚本
#!/bin/bash
4 V8 X4 m* g% T# [, n
! I5 c6 }+ O, l% n% @+ G7 X
source a.sh
9 z4 _! f# H+ v; n" g! r* f9 b* F$ B8 c: L( k$ w$ Z- Z  @  h
func_a $1
( ^0 J0 F0 n6 Z- Lecho "varaVAR_A"
: N) [, a0 x2 J1 B: [- recho "b.sh...pid$"
* J5 s6 U4 w7 @8 m
执行 ./b.sh 5 命令,结果如下
[root@ecs-centos-7 ~]# ./b.sh 5
* s2 F1 V$ A3 oa.sh...pid:21485,param:5
+ g/ J' E' N0 k) {) q7 avara:10
3 N8 M4 ]) C1 e+ |4 ^! E  Kb.sh...pid:21485& u5 \, r. d) A8 I. B* l% X4 m$ x
两个脚本中的 $$ 是指执行脚本的进程ID,从结果可以看出,a.sh 和 b.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.sh 中 source a.sh 修改成 . a.sh , 执行 ./b.sh 5, 结果依然是相同的
由于 sh 外部脚本的方式是当前脚本和外部脚本在两个不同的进程中执行,所以当前脚本不能直接使用外部脚本中的函数和全局变量
修改 a.sh 和 b.sh 脚本, 内容如下
a.sh脚本
#!/bin/bash/ z1 P/ e% @/ s( H) D

/ y: |( N# F4 b" ?test_a()
( Y- p* F" R! G4 ^) N- ], l& X{
) k( P6 h3 i  S. J8 _4 X7 B: V: b  echo "a.sh...test_a"
) _$ ^7 @& z8 X' Q- ~}  k- m% J5 T* U8 |

: V9 }# `3 P- `) G7 W. Techo "a.sh...pid$"
3 C  ?, e4 j3 g9 j0 l4 l
b.sh脚本
#!/bin/bash
' K( F$ }' |! V
6 r) ~6 L0 R  T) T
sh a.sh
6 K1 ^# L; W  I% k
! E/ q1 R; E# A- H) p6 ^echo "b.sh...pid$"
0 Y3 O9 a2 ]) n7 @- b
5 k8 r, A3 `- jtest_a
# N" C; j2 i0 U  @- H- i
执行 ./b.sh 命令,结果如下
[root@ecs-centos-7 ~]# ./b.sh
8 P; Y$ h6 V3 d; o' L, R  J, ja.sh...pid:218183 K8 w. D4 E3 \* N4 f
b.sh...pid:21817
8 z; P2 R( n% b$ }5 ?+ ~" a./b.sh:行7: test_a: 未找到命令; E5 `' O* I% O- E  A* c- A- r* N" }
从结果可以看出,执行 a.sh 和 b.sh 的进程ID是不同的,b.sh脚本进程找不到test_a函数,所以在b.sh中调用test_a 函数会提示 未找到命令
调用外部脚本中的函数
上一节讲到 sh 外部脚本 的方式无法直接使用外部脚本中函数和全局变量,下面提供几种方法可以解决这个问题
  • case 分支选择
    / _& y( ?* l8 P; |& P# \# o
这种方法类似于程序代码中的 switch case 语句,通过switch 选择不同的分支从而执行不同的逻辑,shell脚本中是使用case关键字来实现的
a.sh脚本
#!/bin/bash6 Y* w6 o1 J" z% V3 L9 p
. E5 N: F  _! }1 C4 x! r3 l6 W
VAR_A=10
0 f, [* n6 G  V" l. v
& ~: w4 }* Q. Z  }8 {9 W1 \test_a()' {& N- L  I0 b" K7 A- C
{$ E% }' k3 A" a1 i3 K
   echo "test_a..pid$,p11,p22", R/ z) f7 _: R$ y& ^
}
4 v3 r3 [1 q# k* a  c; zget_var()
% n9 t. c3 W3 I  A3 C! r{6 D! }* h) q* [- v- n) K
  echo ${VAR_A}
$ v( L( r+ F9 S1 e}
, t' T( s( `5 H! G9 y+ F. x
7 V/ r$ `: i# X) D: p* N* bcase "$1" in/ z6 @& m1 ]% u: v
    ta)# P! z0 S' Y/ I5 N# P
      test_a $2 $3; [3 \6 J7 {6 `9 w
      ;;
/ j( F! l+ x! q2 b. ?; s    var)- ~% N/ z$ x3 X6 M% U1 D
      get_var% l' ^3 C, S  |- [) k4 t
      ;;
3 f+ y$ Q# H* a; u, R   *)/ }  u3 T8 _/ J2 j. n
      echo "parameter err..."
' b4 p! r' d7 X+ ?/ m: `esac% t. c# _( k7 x+ l
b.sh脚本
#!/bin/bash6 ^3 l; G0 o) U3 v+ O+ l. N

0 s! E/ O: s' n1 ?echo "b.sh...pid$"
0 p! n1 t5 |% ]# ~. Q* r, ?
3 V% C  c3 M- w; L: g' _/ Z6 Ash a.sh ta 3 5
+ {4 m- ~/ ^- y  I5 S8 @
* }% i% |& a+ K  B! p( dret=$(sh a.sh var)% h" N5 c) Q% ?( L# R& ]! X

' N* \1 B( H9 [$ K4 cecho "ret:$ret"6 S; D* ^' f  Q
执行 ./b.sh 命令,结果如下
[root@ecs-centos-7 ~]# ./b.sh
0 S3 v5 Y) g# r" S. j/ Bb.sh...pid:248130 v4 P/ W) m) \8 }) T  K
test_a..pid:24814,p1:3,p2:5( Y+ D/ ]6 ~3 ~+ e5 |! P
ret:10
3 z) }% B5 Q, s$ d; E, a! S, U0 M
脚本b.sh一开始打印了调用自身的进程ID
sh a.sh ta 3 5 语句是调用a.sh脚本,传入的三个参数分别是ta, 3, 5 ,执行a.sh时,传入的第一个参数 ta经过case匹配之后调用 test_a函数,并把剩下的两个参数 3和5作为参数传入函数
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调试日志,那么调试日志也会一起返回
  • 函数调用模板

    " L, @' V& H6 O. E; |, c9 N
上面介绍的用 case 关键字去匹配调用不同的函数有一个缺点,每次a.sh脚本中增加一个函数的时候,case 就需要添加一个分支,分支里调用不同的函数,还需要注意函数是否有参数传入以及参数数量是否正确
我们可以在每个供外部调用脚本的尾部加上以下的语句,就可以解决上述问题, 具体语句如下
if [ $# -ge 1 ]; then; g. @3 |& p) ]
   name="$1": [" {! U: J5 p
   shift 16 s5 D8 X, {3 n2 ]
   $name "$@"
5 O  o  u* ?* t$ \fi
$ b  S! _; v" w, ^5 D1 v  q. L$ W
上述语句首先判断调用脚本时传入的参数数量,只有参数数量大于等于1才有效,传入的第一个参数表示函数名字,从第二个参数到最后一个参数都会作为参数传入到函数中
这里的 shift 1 是把传入脚本的参数左移一个位置,比如:传入脚本参数有 $1 $2 $3三个参数,左移一个位置之后, $2 移动到 $1 的位置,$3 移动到 $2 的位置,参数数量变为2了
原因: 传入脚本的参数中,第一个参数是函数名字,从第二个参数起才是函数的参数,如果不做左移处理,第一个参数函数名字也会作为参数传入到函数中
下面是完整的脚本内容
a.sh脚本
#!/bin/bash
, R8 @  i4 h! x! ~8 t5 A* F

5 e0 E/ C- p9 n! v6 c7 [' H& mVAR_A=10& o- W9 ]# O- X' g2 G3 K

) T% C! X% F- U4 ctest_a()
( p) ~. }% E% s" F# y{6 Y4 @% P1 D+ M" ?# G1 z
   echo "test_a..pid:$$,p1:$1,p2:$2"* r9 ~; F- w6 j
}
$ p3 I+ s) F! {  `% ]: l2 T+ t1 Z, f! N9 O* O
get_var()
% ^) K% [$ @& _$ O- I& k{
5 Z; R8 V; H2 W/ X) }+ i  echo ${VAR_A}  @! r# m0 \* k' R( Q: [/ O5 L
}
  Z+ U* ^/ ^. q0 e' d( ^1 P' _) P* S: N- D5 E0 H0 o
if [ $# -ge 1 ]; then
  K% i+ @8 H6 A# `  _7 z) v   name="$1"6 z, G$ ~- w, G" X" e" N
   shift 1
$ p; p1 f, F5 k3 c+ C% s   $name "$@"
5 R0 \- \9 H0 S  }fi1 w2 Z; f/ A' q+ T" @
b.sh脚本
#!/bin/bash/ {9 k) C1 a; t9 A

& D: u  z% B$ Kecho "b.sh...pid:$$"
  G" t) R7 m8 Z, L6 C8 k
1 s9 X; E9 C, |  X$ V* ^/ [sh a.sh test_a 3 59 h" s) b! h8 j: a

1 Q' @$ R( _$ C5 J/ x" a7 ^0 q1 |9 iret=$(sh a.sh get_var)6 M- g' q3 f, i7 x& y& F
执行 ./b.sh 命令,结果如下
[root@ecs-centos-7 ~]# ./b.sh % x* s" A: i. e$ s1 `) _) r
b.sh...pid:25086
9 l- K9 y6 b2 b& Btest_a..pid:25087,p1:3,p2:5+ ~$ I4 h6 Q& h
ret:10
' W2 T2 T/ w. W
可以看出,结果和上面 case 的方法是一样的
现在其他脚本中都可以通过 sh a.sh 函数名 参数列表 这样的方式调用 a.sh 脚本中的函数了,通过 $(sh a.sh 函数名 参数列表)的方式获取 a.sh脚本函数的返回值
  • 两者的优缺点

    % n; W: ?  e( Y1 o
与case分支选择的方式相比,函数调用模板的优点是调用者只需要关心复用的脚本中函数名、函数传入参数、函数返回值就可以直接使用
缺点是如果有多个脚本都调用了复用脚本中的函数,当复用脚本中函数名变更时,需要修改所有调用了它的地方
函数调用模板方式的缺点恰恰是case分支选择方式的有点,case分支选择的方式时根据传入的字符串参数调用不同的函数,这里的字符串参数相当于函数的别名,只要这个参数保持不变,脚本中的函数名字可以任意变更
上述的优缺点比较只是一个相对的比较,实际应用中下不会很明显,大部分情况两种方式都可以使用
小结
在编写shell脚本的过程中,经常会遇到一些莫名奇妙的问题,有些问题就算挠破头皮都不知道如何解决,脚本复用可以把一些公共功能提取出来,形成一个个的功能模块,不仅有助于减少我们编写脚本时犯的错误,而且对后期的脚本维护很有帮助

6 u- G/ w/ B* |! Y
赞 收藏 评论0 发布时间:2020-11-14 14:25

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版