ZMonster's Blog 巧者劳而智者忧,无能者无所求,饱食而遨游,泛若不系之舟

编写Shell脚本时的一些小技巧

自工作以来,写了不少脚本,有自己临时需要而写的,也有给测试妹子编写的工具。在这个过程中,碰到了许多问题,当然也就学习到了很多啦。有些零零碎碎的细节,花大篇幅讲有点没必要,就整合在这篇文章里吧。

使用特殊变量

  1. $#

    $#表示当前参数列表的长度,对需要参数的Shell函数,这个变量同样有效

  2. $*与$@

    这两个都包含了当前所有参数,但当用 双引号将这两个特殊变量括起来 后,它们之间会产生差异。

    编写一个简单的脚本spe_arg.sh,内容如下:

    #!/bin/bash
    
    function arg_num(){
        echo $#, $1
    }
    
    arg_num "$*"
    

    执行它,随便给点参数:

    ./spe_arg.sh a b c d
    

    得到的结果是:

    spe_arg-1.png

    而将脚本中的$*替换成$@后,得到的结果是:

    spe_arg-2.png

  3. $$

    这表示当前的进程号。我经常将其用于生成临时文件,由于每个脚本运行后,其进程号都是唯一的,可以保证不会和其他脚本产生的临时文件发生冲突。

  4. $?

    上一条命令的返回值,为0则表示上一条命令正常执行,否则就是出错了。通过这个值可以方便地进行错误处理。

  5. $0

    表示当前脚本的名字

  6. $1, $2, … $9

    分别表示第1个、第2个…第9个参数

条件表达式

在Shell脚本中,有三种条件表达式的形式:

  1. 一对中括号括起来的条件表达式,如

    [ 0 -gt 1 ]
    

    使用这种方式,在进行字符串比较时,用于比较的运算符"<"和">"必须转义,即必须表示为"\<"和"\>"。

  2. 用一对双中括号括起来的条件表达式,如

    [[ 0 -gt 1 ]]
    

    使用这种方式时,字符串比较的运算符不需要转义。因此,应该尽量使用这种方式而不是第一种。

    另外,无论是第一种方式还是第二种方式, 条件表达式前后一定都要有至少一个空格 ,即下面这样的是非法的:

    [0 -gt 1]
    [0 -gt 1 ]
    [ 0 -gt 1]
    
  3. 用一对双括号括起来的条件表达式,如

    (())
    

    这种方式用于算术比较,写在其中的条件表达式应该按照类似C语言的风格来表示,即不需要遵循上面两种方法的限制,也不需要使用dollar符($)来表示变量的值。

    当然,双括号不只用于条件表达式,还可以在其中进行四则运算,for循环也可以使用它。

变量使用

  1. 局部变量

    在函数中,应该尽量将使用到的变量定义为局部变量,以下做法可以达到该目的:

    local var=""
    
  2. \(var与\){var}

    在可能产生歧义的地方,应该使用后一种方式。所谓的歧义,可能这个词我用得不太准确,还是举个例子来描述一下吧。

    以前阵子我写的一个脚本为例,当时我定义了一个循环计数变量,命名为"i",然后在每一次循环时,根据其值创建一些文件,大致是下面这个样子

    i=0
    for i in $(seq 1 3);do
        now_file=$ROOT_PATH/$i_wavlist
        > $now_file
    done
    

    我的本意是产生诸如"1_wavlist"、"2_wavlist"这样的文件,但没注意下划线也是变量名合法字符这个事实。所以这样才是正确的:

    i=0
    for i in $(seq 1 3);do
        now_file=$ROOT_PATH/${i}_wavlist
        > $now_file
    done
    
  3. 在sed/grep/awk中使用Shell变量

    sed和grep可以一起说,因为是同样的问题。在sed/grep的表达式中使用Shell变量,如果想得到期望的结果,应该用双引号而不是单引号将表达式括起来。这是因为在Shell中,双引号是 弱引用(Weak Quotes) ,而单引号是 强引用(Strong Quotes) ,在一对单引号中的内容,会作为字符串内容原原本本地输出,也就是说,Shell的取值操作符dollar符($)在一对单引号中,也是被视为一个普通字符的。

    不仅仅是取值操作符,像转义符(\)、反引号符(`)等特殊字符,在一对单引号中,其特殊意义会统统失效。

    awk中的情况与前面两者类似,但又有所不同。由于awk的表达式中也用dollar符($)来取awk处理时每个域(field)的值,所以在awk中使用Shell变量,必须要区分开Shell变量与awk变量。

    第一种方法是通过"-v"选项定义一个awk变量并将Shell变量赋值给它,如下:

    awk -v var="$shell_var" '{print var}' a.txt
    

    第二种方法是用双引号来将awk的表达式括起来,但要注意的是,当用双引号括起awk表达式时,像"$1"这样的都会被认为是在获取Shell变量的值,即在awk处理这串表达式前,Shell会将所有以dollar符开头的东西按照Shell中取值规则取值,然后替换成这个值,于是本来想让awk打印第一个域(field)的意图就会落空。

    解决这个问题的方法是用转义符将不是使用Shell变量的地方转义,如下:

    awk "{print \$1, $shell_var}" a.txt
    

    第三种办法是通过额外的引用来达到目的,如下所示:

    awk '{print $1, "'$shell_var'"}' a.txt
    

处理文件

文本处理这里就不说了,只是说一下处理文件时的一些小细节。

  1. 文件属性判断

    条件表达式中,'-e'用于判断文件是否存在,'-d'用于判断是否是一个目录,'-s'用于判断文件是否存在但内容为空。这三个都是我比较常用的。

  2. 获取文件绝对路径

    在一些情景下,我们需要获得文件的绝对路径,比较蠢的办法是:

    dir=$(dirname $file)
    full_dir=$(cd $dir && pwd)
    

    但其实还有更好的办法,那就是使用'readlink'这个命令:

    dir=$(dirname $(readlink -f $file))
    

后记

暂时先这样吧,以后还有东西,会继续更新本文。