Bash高级编程:基础

一、脚本

Bash脚本由Bash能够解释的命令组成。所有的注释以#开头直到该行结尾:

echo abc          # 显示 abc
echo abc;         # 每一行后面的分号是可选的
echo a; echo b    # 在这种情况下,第一个命令后的分号不可省
echo a &          # &和;都是行结束符,但&的意思是当前命令放到后台执行
echo a &;         # 错误,&和;只能用一个
;                 # 错误,跟高级程序语言不同的是,行结束符不是可以随意添加的

使用/bin/bash script.sh命令来运行脚本不需要脚本有可执行权限。使用./script.sh来运行脚本则需要先使用chmod +x来给脚本文件增加执行权限。

如果脚本文件有执行权限,则脚本第一行以#!开头可以指明该文件运行的解释器的绝对路径,包括/bin/bash, /bin/csh, /bin/awk -F, /usr/bin/python等,甚至也可以是/bin/cat, /bin/rm等。这样的话,当使用./script.sh命令来运行该脚本时,bash会去调用脚本指定的解释器来运行该脚本。

二、变量

Bash脚本中的变量在使用前无需定义。定义的格式为:

name=Value        # = 左边不能有空格,否则会被视为命令 name 的参数

随后可以使用该变量、改变该变量的值,或者删除该变量:

echo "${name}"    # 引用变量的标准形式
echo "$name"      # 在没有歧义的情况下,{}可以省略
name=abc          # 给变量赋一个新的值
name=             # 将变量置空,但是变量还存在
unset name        # 将变量从当前的 shell 中删除

在脚本中定义的变量都是只在当前shell中可见的,对其子shell不可见。若需要变量对子shell可见,则需要将变量导出到环境中:

export var1=XXX   # 定义时直接导出
var2=YYY
export var2       # 先定义,再单独导出
command           # 在command中能看见$var1和$var2的值

或者可以使用env来运行command,也能达到上面的效果:

env var1=XXX var2=YYY command
var1=XXX var2=YYY command       # env可以省略
echo $var1 $var2                # 空;
                                # 与上面不同,这样不会把var1和var2加入到当前shell中

三、位置参数

运行脚本的命令行给出的参数可以在脚本中通过位置参数得到。第一个位置参数是$1,第二个是$2,以此类推。若位置参数大于等于10,则必须用${10}来引用。

脚本名本身不算作位置参数。

可以使用shift指令让所有的位置参数左移,即$1被删除,$2变为$1$3变为$2,以此类推。

可以使用set指令重新设置位置参数:

set -- a b c    # $1, $2, $3 依次为 a, b, c
# -- 表示该命令(set)的选项已经结束,之后以-开头的参数也不解释为选项

四、特殊变量

: $*  # 扩展为所有的位置参数的字符串
: $@  # 扩展为所有的位置参数的列表
: $#  # 位置参数的数量
: $0  # 当前脚本的路径
: $$  # 当前进程的PID
: $?  # 上一个命令的退出码
: $_  # 上一个命令的最后一个参数
: $!  # 上一运行的后台进程的PID
: $-  # 当前的option flags

遍历脚本的所有参数:

for option in "$@"   # or: for option
do
    : do something
done

五、输入输出

echo可用于简单的输出。它依次输出自己的各个参数:

echo a    b c      d     # a b c d
echo "a    b c      d"   # a    b c      d
echo "-n"                # 没有输出,因为 echo 将 -n 视为它的一个选项

printf用于格式化的输出,脚本中应该坚持使用printf

printf "%s\n" a b c "d e"

会打印出:

a
b
c
d e

如果格式化参数不够,printf会对剩下的参数重复使用前面的格式化参数。

printf还可以通过-v选项将字符串输出到变量中,即给变量赋值:

printf -v num1 "%d" "1"
printf "%d\n" $num1       # 打印1

read用于读取标准输入并将读到的内容赋值给指定的变量:

read line         # 默认读取一行的内容赋给line变量,如果不指定变量,则默认存放在 REPLY 变量中
read a b c rest   # 读取一行,前三个空白分隔的词分别赋给a, b, c,剩下的赋给rest
read -r line      # 在读取一行的时候保留行内的反斜杠\

六、函数

符合POSIX标准的函数定义形式为:

name() <compound command>

其中,<compound command>是被{}或者()括起来的一连串的命令,或者是被(())或者[[]]包含起来的表达式,甚至也可以是一个块级的casewhile等。

ksh和bash也允许在name()之前再加上function关键字,不过这个关键字也没太大用处。另外,name后面的()永远都是空的,因为函数的调用也就是命令的调用,是依靠位置参数来传参的。

在函数里使用return <code>来设置一个0到255的退出码,调用者可以通过$?得到。

函数只是为<compound command>定义一个指代的命令而已,因此,与管道重定向一样,只有当函数体是用()包围时,该函数的调用才会产生子shell,这时,在函数体里定义的任何变量,在当前脚本中都是访问不到的。但如果函数体是用{}包围的,则该函数是在当前shell中被执行,该函数中定义的任何变量在该函数调用完成之后对脚本是可见的。这也是shell函数返回内容的方法(return只设置退出码,不能返回内容)。另外,如果要在{}中定义局部变量,则要使用local关键字:

f() {
    local A B=3 C
    echo $A $B $C
}