shell私房菜part3

来源:互联网 发布:不喜欢rpg游戏知乎 编辑:程序博客网 时间:2024/05/16 02:05

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一个表到本地文件逻辑,那么先写这一段

 

#!/bin/bash 帅气的起手式

2

dbSever=’blablabla’    各种数据库连接参数,注意单引号啊,简单的字符串赋值就这么写

dbName=’blablabla’   注意赋值的时候“等号”两边无空隙

dbUser=’blablabla’    

dbUpsw=’blablabla’   

localFileBase=’/tmp/storedump’ 放dump文件的目录

8

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函数的样式、调用和参数传递使用,要仔细看注释啊。

 

#!/bin/bash 

2

dbSever=’blablabla’   

dbName=’blablabla’   

dbUser=’blablabla’    

dbUpsw=’blablabla’   

localFileBase=’/tmp/storedump’ 

8

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

[ "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 &

0 0