平常工做中编写shell脚本是再日常不过的了,那编写过这么多的shell,当咱们碰见曾经写过的相关的处理函数时,如何来复用?直接拷贝相应的代码仍是“引用过来”,或者是在编写一个功能相对复杂的shell时,如何作到模块化,如何组织复杂的函数调用?docker
如何导入复用和模块化,就是这里要一块儿学习的。shell
咱们会按照如下几个部分来学习: 编程
一、shell执行方式不一样的效果不一样bash
二、导入模块编程语言
一、shell执行方式不一样的效果不一样ide
第一种:sh test.sh模块化
这种方式会在咱们当前进程中,再新建一个子进行去执行这个test.sh 脚本。咱们能获取到的东西,也就是这个test.sh执行完的结果或者输出。函数
root@docker-host-03:~#more test.sh 学习 #!/bin/bash测试 s="thisis a test file" root@docker-host-03:~#sh test.sh root@docker-host-03:~#echo $s root@docker-host-03:~#sh test.sh root@docker-host-03:~#echo $? 0 |
第二种:sourcetest.sh 或者. Test.sh
这种方式至关因而将test.sh的内容拷贝到当前进程中来执行。这样咱们能获取到的内容就不只仅是这个test.sh执行完成的结果。咱们还能获取到在这个脚本中定义的全局变量,定义的功能函数。由于咱们是将脚本的内容拷贝到当前文件中。
那这种方式就和咱们不少编程语言中的模块导入相似。咱们来验证下是否是。
root@docker-host-03:~#. test.sh root@docker-host-03:~#echo $s this is atest file root@docker-host-03:~#s=0 root@docker-host-03:~#echo $s 0 root@docker-host-03:~#source test.sh root@docker-host-03:~#echo $s this is a test file #咱们能够看到这里,能够获取到变量s的内容。 |
二、导入模块
2.1)先导入一个最简单的
基于上面第一点咱们提到的两种shell的执行方式中的第二种。咱们利用这种“拷贝”的特性就可以实现将之前写过的功能,进行导入
好比咱们写了很简单格式化输出log的函数:
root@docker-host-03:~# cat test1.sh #!/bin/bash
MODULENAME=$(basename $0) LOGFILE=/tmp/logfile-`date +%Y%m%d`
log_info() { #[2017-03-31 12:00:00 ] - TextName - The log message Localdatetime=`date "+%Y-%m-%d %H:%M:%S"` if[ "$1" ];then echo "[ ${datetime} ] - ${MODULENAME} - $1 " | tee -a${LOGFILE} else return 1 fi } |
上面这个log_info 函数会格式化刷出咱们的日志内容。咱们能够在多处复用。
咱们这里的Main.sh脚本和test1.sh在同一个目录里面。咱们main.sh先导入test1.sh的内容,而后使用test1.sh中已有的变量,功能函数。咱们来看下main.sh的内容。
root@docker-host-03:~# cat main.sh #!/bin/bash
#咱们在这里经过. Test1.sh 或是source test1.sh的方式将test1.sh导入进来。 . test1.sh main() { #咱们在这里就能够直接使用log_info函数了。 log_info 'This is the main shell' } main root@docker-host-03:~# ./main.sh [ 2017-03-31 15:11:23 ] - main.sh - This isthe main shell root@docker-host-03:~# more/tmp/logfile-20170331 [ 2017-03-31 15:11:39 ] - main.sh - This isthe main shell
|
2.2)若是咱们须要从其余地方导入其余函数
上面咱们的main和test1是在同一个目录中,因此咱们在main 中使用的是相对路径。若是咱们须要导入的“模块”test1.sh 是放在其余的目录?或者说咱们的模块存在层级关系?咱们有应该如何来引入呢?
1)因咱们在引入的时候,系统默认会在环境变量$PATH中去寻找咱们要导入的文件。一种作法是咱们将咱们须要用到的公共模块放到$PATH包含的路径中。或者咱们添加一个公用的路径到$PATH环境变量中,而后将咱们须要用到的公有模块放到公用文件夹下。
如:
root@docker-host-03:~/test_shell# cat main.sh #!/bin/bash
. test1.sh . Config.sh main() { log_info 'This is the main shell' readConf echo${conf} } main root@docker-host-03:~/test_shell# echo$PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games #在这里咱们能够看到咱们将两个模块放在了PATH的路径下面。 root@docker-host-03:~/test_shell# ls/usr/local/bin/test1.sh /usr/local/bin/test1.sh root@docker-host-03:~/test_shell# ls -l/usr/local/bin/Config.sh -rw-r--r-- 1 root root 59 Mar 31 15:41/usr/local/bin/Config.sh
|
这里我直接将文件拷贝到了$PATH有包含的目录中。
2) 咱们将模块所在的绝对路径export到PATH中:
如:
root@docker-host-03:~/test_shell/module#pwd /root/test_shell/module root@docker-host-03:~/test_shell/module#tree . ├── Config.sh └── test1.sh
0 directories, 2 files root@docker-host-03:~/test_shell/module# cd../ root@docker-host-03:~/test_shell# moremain.sh #!/bin/bash
#这里咱们给了一个绝对路径 MODUPATH=/root/test_shell/module export PATH=$PATH:${MODUPATH}
. test1.sh . Config.sh main() { log_info 'This is the main shell' readConf echo${conf} } Main
|
3) 动态的加载模块
固然若是咱们在编写一个功能相对复杂的shell的时候,咱们将会将这个一个整个复杂的带有结构关系的shell打包,拷贝到任何一个地方去执行。这样咱们就没有办法事先知道咱们的绝对路径是怎样的。好比这样的目录结构:
root@docker-host-03:~/test_shell#pwd /root/test_shell root@docker-host-03:~/test_shell#tree . ├── main.sh ├── module │ ├── Config.sh │ └── test1.sh └── module2 └── mail.sh
2directories, 4 files root@docker-host-03:~/test_shell#more main.sh #!/bin/bash
#这里动态的获取到咱们的绝对路径,并把模块的路径拼凑出来。 MODUPATH=$(dirname$(readlink -f $0))/module echo${MODUPATH} exportPATH=$PATH:${MODUPATH}
.test1.sh .Config.sh main() { log_info 'This is the main shell' readConf echo ${conf} } Main
|
4)如何避免重复屡次的导入某个模块
咱们要说source,其实就是将内容拷贝到同一个地方执行,那咱们能够在模块中加入一个判断条件。若是发现咱们模块中的某个变量已经存在,咱们就return出去,再也不导入后续的内容。
好比这样:
root@docker-host-03:~/test_shell#more module/test1.sh #!/bin/bash
#这里我注释掉的内容就是。并且这个变量名称,尽可能按照某种规则确保是惟一的变量名称。 #if [${m_log} ];then # return 0 #fi #m_log="m_log" #这一行输出是为了测试是否屡次导入了。 echo"TEST IS" MODULENAME=$(basename$0) LOGFILE=/tmp/logfile-`date+%Y%m%d`
log_info() { #[ 2017-03-31 12:00:00 ] - TextName - The logmessage datetime=`date "+%Y-%m-%d%H:%M:%S"` if [ "$1" ];then echo "[ ${datetime} ] - ${MODULENAME}- $1 " | tee -a ${LOGFILE} else return 1 fi }
|
这里咱们在注释掉的状况下,看看屡次导入是什么反应。
root@docker-host-03:~/test_shell# more main.sh #!/bin/bash
MODUPATH=$(dirname $(readlink -f$0))/module echo ${MODUPATH} export PATH=$PATH:${MODUPATH}
. test1.sh . Config.sh main() { log_info 'This is the main shell' readConf echo${conf} } main root@docker-host-03:~/test_shell# moremodule/Config.sh #!/bin/bash
. test1.sh readConf() { conf="Read the config file" } root@docker-host-03:~/test_shell# ./main.sh /root/test_shell/module TEST IS TEST IS [ 2017-03-31 16:51:09 ] - main.sh - This isthe main shell Read the config file #咱们能够看到这里屡次输出了TEST IS内容。 |
#将咱们上面注释掉的内容去除,如今虽屡次导入,但实质上只会导入一次。这样也会避免屡次循环引用。
root@docker-host-03:~/test_shell# ./main.sh /root/test_shell/module TEST IS [ 2017-03-31 16:56:31 ] - main.sh - This isthe main shell Read the config file #当咱们去掉了注释部分,这里就不会出现屡次重复载入了。 |
-----------------------------小结-----------------
其实这里的模块复用,其实就是经过source 或则. 的方式将本来的模块内容“拷贝过来”,只是在使用的过程中,须要注意模块路径的问题,还有层级,和屡次调用的问题。
至于功能复杂的shell,我想基本也不会复杂到没法接受,基本按照必定的组织发方式就能变得清晰。而且提升复用。
你们能够本身积累一些比较经常使用的函数,须要用到的时候,导入,不用重复编写。好比日志,邮件告警等等。
----------------------------------------------------