shell私房菜part1 传送门
shell私房菜part2 传送门
命令行下工作的shell和写成文件的shell脚本没有本质的不同,就重用性来看,写成文件的shell当然很好,天然可重用无压力;
一条命令行shell使用后可以通过上下箭头再翻出来,还可以通过history命令找出来,history可以记录你最近执行的n条命令,我的开发机上,n=2000,so不要用shell干坏事哦。
当需要用shell处理的事情变得复杂,把shell写在文件里是明智的选择。此时你将使用到shell的变量赋值、判断、函数,请听我一一道来。
首先来看看写shell脚本文件的“起手式”
#!/bin/bash
Shell有多种啦,还是因为出现的时间久,大牛多,无组织无纪律很容易就搞到自成体系。
在linux系统下,默认的shell是bash,我们基本上只玩linux,那么就把bash弄明白就好。
在第一行写上这行文字目的就是告诉内核,明确用系统中的特定位置的bash来执行之后的shell脚本。
起手式打好,我们来说说这次的case :
- 我当时要为“搜索引擎”准备xml数据,需要从数据库中把数据dump出来放到文件里,供后续的处理程序计算使用。
- 店铺数据比较多啊,数据库中是分了256张表,表名从store000到store255。
- 这个case是有效率要求的,ASAP(As Soon As Possible)。
从数据库dump数据要用到mysqldump命令,需要知道数据库的ip或是域名,数据库名,用户和密码,现在这些都是已知的,有0到255编号的这么些表需要dump,写个循环就好,但是ASAP怎么办?
串行的一个一个dump应该不如并行多个一起dump快,并行也要有度,弄到和网络、数据库吞吐、磁盘写入里较短的那块板一样就好。
不管怎样都要用到dump一个表到本地文件逻辑,那么先写这一段
1 #!/bin/bash 帅气的起手式
2
3 dbSever=’blablabla’ 各种数据库连接参数,注意单引号啊,简单的字符串赋值就这么写
4 dbName=’blablabla’ 注意赋值的时候“等号”两边无空隙啊。
5 dbUser=’blablabla’
6 dbUpsw=’blablabla’
7 localFileBase=’/tmp/storedump’ 放dump文件的目录
8
9 tableName=’store000’ 先dump第一个表看看
10 dumpFile="$ {localFileBase} /$ tableName "这里用到双引号字符串,双引号的能耐比单引号大,它能吧带$号的变量展开替换,${var}是最正规的变量引用方法,$var在无歧义的时候也可以使用,dumpFile在赋值执行完成后的值是/tmp/storedump/store000
11mysqldump –C --extended-insert=false -h${dbSever} -u${dbUser} -p${dbUpsw} ${dbName} ${ tableName} > ${dumpFile} 这是真正干活的调用,mysqldump用到的各个参数都使用变量替换好了,使用输出重定向符>将dump的内容写入$dumpFile,也就是/tmp/storedump/ store000
上面这11行shell脚本代码就可以完成第一张表的dump了,我这里就演练了一下shell的赋值和变量的使用,还有帅气的起手式哈。
再回头看一下这短短11行代码,3到7行是在做dump任务的那些常量的赋值,9到11行是在为具体dump第一张表写逻辑。9到11行适合包装成函数被调用——dump第n张表到本地文件。就一个输入参数n,那么我们就来包装一把。
关于shell函数的样式、调用和参数传递使用,要仔细看注释啊。
1 #!/bin/bash
2
3 dbSever=’blablabla’
4 dbName=’blablabla’
5 dbUser=’blablabla’
6 dbUpsw=’blablabla’
7 localFileBase=’/tmp/storedump’
8
9 dumpTable2File() { 函数名(){函数体}这就是shell中函数标识,我把函数命名为dumpTable2File,我期望它被调用的时候跟一个参数n,用来表示需要dump的表的序号。但是和其他语言不一样,参数的规定或是声明不出现在这里,应该这样说,函数需要的参数不用规定和声明。
11 tableName=”store$1” 这行和前一个示例就不太一样,首先用的双引号,然后用$1引用了第一个参数,嗯,就是这么简单粗暴,不讲道理。很少有程序语言这样处理参数。$2是第二个参数,$3是第三个,类推就好。如果你想知道实际传递的参数的个数,用$#就好了。
12 dumpFile="$ {localFileBase} /$ tableName "
13 mysqldump –C --extended-insert=false -h${dbSever} -u${dbUser} -p${dbUpsw} ${dbName} ${ tableName} > ${dumpFile} 函数中可以引用shell中的其他的变量哦,在函数外定义的其他的变量可以看做就是全局可见的了,如果你修改了,跳出函数它们的值也是跟着变的。
14 } 函数结束,大括号闭合
15 for index in `seq 0 255` 循环串行遍历0到255个数据表,调用dumpTable2File函数
16 do
17 dumpTable2File $index 函数的调用,需要传递的参数$index直接跟在函数名的后面,留个空格就好,如果要跟第二个参数,打一个空格继续写,如果参数中就有空格,把它装在单引号中。
18 done
上面这18行代码其实基本上可以串行地完成dump工作了。
我演练了shell函数的组织和调用。它看起来怪怪的,基于约定的,严肃的程序员可能要吐槽参数的传递怎么如此草率,没有规矩。我简单替大牛们辩解一下,shell是解释执行的,shell要KISS力求简单,参数的传递基于约定(虽然是一堆难看的变量)能work,够简单。
上面代码其实还是无法工作的,原因在于第11行tableName=”store$1”,回忆一下我们的case,表的名字是从store000到store255。所以循环中,当$index是0到99的时候,tableName的赋值是不对的,缺少了一些字符’0’。See,我们需要store000,当index等于0的时候我们得到store0。那么我终于讲到if了,在写了快7000字之后才讲到它,挺怪异的哈,因为shell的if确实是个怪东西。
逻辑很简单,我们在函数dumpTable2File里用if,判断两个逻辑。
其一保证参数是至少有一个的;
其二参数1的值在小于10的时候补2个字符’0’,在小于100的时候补1个’0’。
注释已经很难说清楚if了,所以先概述一下,再上代码
if语句其实本来的面目是这样的
if TEST-COMMANDS; then COMMANDS; [ elif TEST-COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
if后面的TEST-COMMAND是一个shell命令,如果它的返回值为0那么执行then后面的命令。elif是else if的简写,fi是if语句的结束符号,这些关键字体现了大牛们特立独行的风格。
上面这句话提到了shell命令的返回值,这个要插入说一下,其实每一条shell命令执行后都会有一个返回值,它可用通过$?来引用到,你是否要吐槽发明shell的大牛明显是个无节操的,用这么偷懒的方法,和这么丑的字符,来取得返回值,颠覆了你对程序语言的认知。
还有0在很多的程序语言中等同于false,但是shell就喜欢让它等同于true。
Any way 我来示例一下$?
ls mod.sh;echo $? 我当前目录上有一个文件叫mod.sh,我去ls它,然后打印ls命令的返回值,是一个0。多个shell命令用;间隔就可以出现在一行,其实前面for循环就见过的,表示串行执行各shell命令
mod.sh 这行是ls的输出
0 这行是echo的输出
ls mod.shs;echo $? 当前目录上没有文件叫mod.shs,那么去ls它,再打印ls命令的返回值,是一个非0的值
ls: mod.shs: No such file or directory 这行是ls的标准错误输出
2
返回值得事情插入完毕,接着讲if。
其实就上面语法结构看起来if没有什么值得说的,这不是if奇怪的地方。奇怪的地方在于if后面的TEST-COMMAND可以是一些古怪的东西。
介绍一个shell内建的命令,它是[
你没看错啊,就是一个左中括号,它可不是表达式的一部分,它等同于shell的内建命令test
[空格‘a’空格=空格‘b’空格]是一个shell命令调用,’a’是第一个参数,=是第二个参数,’b’是第三个参数,而]是第4个。这里的4个空格太重要了,我不得不把它们用中文字明显地标出来,因为没有空格,[这个等同于test的命令就得不到足够的参数。
我们来试验一把
[ "a" = "a" ];echo $? 测试字符串”a”和“a”是否全等,测试完成后,打印返回值
0 全等于是返回0
[ "a" = "b" ];echo $? 测试字符串”a”和“b”是否全等,测试完成后,打印返回值
1 不等于是返回1
test "a" = "a";echo $? 和[等价的shell内建命令test
0
test "a" = "b";echo $?
1
["a" = "b" ];echo $? 如果把[和”a”之间的空格拿掉,那么就出错了,shell解释的时候认为你是要调用一个叫做[a的命令,没有这个命令,于是报错。输出如下
-bash: [a: command not found
127
好了,总算说清楚方括号了,它被常常用来做测试判断和if配合,能耐很大,但是显得怪异,特别是你将看到的中间的测试操作符号的时候。
测试符合有很多,有双目有单目的,有用于整数的,用于字符串的,还有用于文件目的。
上面show过的“=”就是一个用在字符串的,双目的测试符号
给一个常用的整数和字符串的测试符号表
测试操作 | 整数 | 字符 |
相同,双目,比如 [ $a –eq 5 ]测试变量a是否等于整数5 [ “$a” –eq “hello” ]测试变量a是否和字符串”hello”全等 | -eq | = |
不同,双目 | -ne | != |
大于,双目 | -gt | > |
小于,双目 | -lt | < |
大于或者等于,双目 | -ge |
|
小于或者等于,双目 | -le |
|
为空(长度为零),单目,比如 [ -z “$1” ]测试第一个参数是否为空,为空返回0,表示true |
| -z |
不为空(长度为非零),单目 |
| -n |
看完这个表格就知道奇怪的地方在哪里了。
我们直觉或是习惯用来做整数判断的=、!=、>、<却是用来做字符串比较的。
而用来做整数判断的是一些奇怪的缩写,eq就是equal,gt就是great then,lt就是less then,ge就是great or equal。
大牛发明的东西就是比较无语啊,大家要习惯。
还有就是有一些常用的单目的文件目录测试符号,也做一个列表,这些好用常用,shell和文件打交道的时候不要太多
[ -f FILE ]如果FILE存在且是一个普通文件则为真。
[ -r FILE ]如果FILE存在且是可读的则为真。
[ -w FILE ]如果FILE如果FILE存在且是可写的则为真。
[ -x FILE ]如果FILE存在且是可执行的则为真。
[ -d FILE ]如果FILE存在且是一个目录则为真。
花了一页半的文字解释了if和怪异的TEST-COMMAND,解决case中的问题其实就小意思了。
回忆一下case里要用if解决的两个问题:参数是至少有一个的;参数1在小于10的时候补2个字符’0’,在小于100的时候补1个’0’。
Here we go
dumpTable2File() {
if [ $# -lt 1 ] $#是输入参数啊,看看输入参数是不是比1还小啊(好装啊,直接比较0不行吗)
then
echo 'need 1 arg' 如果输入参数是0,打印一串提示
return 从函数中跳出,break和continue在循环中也是能用的啊
fi 判断参数个数的if语句的结束
if [ $1 -lt 10 ] $1是第一个输入参数啊,前面已经判断过,它确实是有的,这里就好放心用了,参数1的意义是数据库表的序号,如果小于10,那么在拼表名的时候补两个0
then then是必须的不能省略
tableName="store00$1" 双引号语法,$1被替换成一个0到9的数字,$1前面有两个0
elif [ $1 -lt 100 ] 如果不是小于10,但是小于100,那么在拼表名的时候补个0
then
tableName="store0$1" 双引号语法,$1被替换成一个10到99的数字,$1前面有一个0
else 如果既不是小于10也不是小于100,那么$1直接就是三位数了直接拼出表名很合适
tableName="store$1" 用$1直接拼出表名很合适
fi
dumpFile="${localFileBase}/$tableName"
mysqldump -C --extended-insert=false -hh${dbSever} -u${dbUser} -p${dbUpsw} ${dbName} ${tableName} > ${dumpFile}
}
Ok如此我们解决了串行dump所有数据表的问题,我们演练了if的使用,那我们那个并行的怎么解决呢。
当确定好并行的个数以后(这个可以实验得出)这件事情就不困难了。方法就是做一个函数来串行处理一批表,然后并行地调用这个函数。
dumpTables(){ 这个函数串行地dump一批表,表的序号从n到m,n由参数1指定,m有参数2指定
for index in `seq $1 $2` 串行dump循环逻辑
do
dumpTable2File $index 调用dump一个表的逻辑,就是上面解释了半天的那个
done
}
dumpTables 0 63 & 调用dump一批表的函数,给参数1是0和参数2是63,表示dump表的序号从0到63。
&不是参数,是shell的基础设施,表示将函数的执行挂到后台。
如果没有&,shell调用函数后就停在这里等返回,这样就还是串行执行的,
有了&,shell调用函数后就不等返回了,接着执行下面的语句。
dumpTables是一个函数,在shell里面call它的时候,其行为其实已经类似调用一个shell命令了
dumpTables 64 127 &
dumpTables 128 191 &
dumpTables 192 255 &
如此我们就实现了并发数为4的dump了。
小结一下,这回的内容比较多一点,
第一页是变量的赋值和使用,
第二页是函数的组织和调用(还有参数啊),
第三、四页是if相关的东西,
最后show了一下后台执行(非同步调用)。
最后完整的程序在最后面贴一下
#!/bin/bash
dbSever='blablabla'
dbName='lablabla'
dbUser='lablabla'
dbUpsw='lablabla'
localFileBase='/tmp/storedump'
dumpTable2File() {
if [ $# -lt 1 ]
then
echo 'need 1 arg'
return
fi
if [ $1 -lt 10 ]
then
tableName="store00$1"
elif [ $1 -lt 100 ]
then
tableName="store0$1"
else
tableName="store$1"
fi
dumpFile="${localFileBase}/$tableName"
echo "mysqldump -C --extended-insert=false -hh${dbSever} -u${dbUser} -p${dbUpsw} ${dbName} ${tableName} > ${dumpFile}"}
dumpTables(){
for index in `seq $1 $2`
do
dumpTable2File $index
done
}
dumpTables 0 63 &
dumpTables 64 127 &
dumpTables 128 191 &
dumpTables 192 255 &