1 函数
函数(function)是可以重复使用的代码片段,有利于代码的复用。它与别名(alias)的区别是,别名只适合封装简单的单个命令,函数则可以封装复杂的多行命令。
函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。如果函数与脚本同名,函数会优先执行。但是函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。
Bash 函数定义的语法有两种。
# 第一种
fn() {
# codes
}
# 第二种
function fn() {
# codes
}
上面代码中,fn是自定义的函数名,函数代码就写在大括号之中。这两种写法是等价的。
下面是一个简单函数的例子。
hello() {
echo "Hello $1"
}
上面代码中,函数体里面的$1表示函数调用时的第一个参数。
调用时,就直接写函数名,参数跟在函数名后面:

下面是一个多行函数的例子,显示当前日期时间。
today() {
echo -n "Today's date is: "
date +"%A, %B %-d, %Y"
}
删除一个函数,可以使用unset命令。
unset -f functionName
查看当前 Shell 已经定义的所有函数,可以使用declare命令。
declare -f
上面的declare命令不仅会输出函数名,还会输出所有定义。输出顺序是按照函数名的字母表顺序。由于会输出很多内容,最好通过管道命令配合more或less使用。
declare命令还支持查看单个函数的定义。
declare -f functionName
declare -F可以输出所有已经定义的函数名,不含函数体。
declare -F
1.1 参数变量
函数体内可以使用参数变量获取函数的参数。函数的参数变量与脚本参数变量是一致的。
$1~$9:函数的第一个到第9个的参数。$0:函数所在的脚本名。$#:函数的参数总数。$@:函数的全部参数,参数之间使用空格分隔。$*:函数的全部参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格,但是可以自定义。
如果函数的参数多于9个,那么第10个参数可以用${10}的形式引用,以此类推。
下面是一个日志函数的例子。
log_msg() {
echo "[`date '+ %F %T'` ]: $@"
}
使用方法如下:

1.2 return 命令
return命令用于从函数返回一个值。函数执行到这条命令,就不再往下执行了,直接返回了。
func_return_value() {
return 10
}
函数将返回值返回给调用者。如果命令行直接执行函数,下一个命令可以用$?拿到返回值:

return后面不跟参数,只用于返回也是可以的。
function name {
commands
return
}
1.3 全局和局部变量
Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。这一点需要特别小心。
# 脚本 test.sh
fn () {
foo=1
echo "fn: foo = $foo"
}
fn
echo "global: foo = $foo"
上面脚本的运行结果如下。

上面例子中,变量$foo是在函数fn内部声明的,函数体外也可以读取。
函数体内不仅可以声明全局变量,还可以修改全局变量。
#! /bin/bash
foo=1
fn () {
foo=2
}
fn
echo $foo
上面代码执行后,输出的变量$foo值为2。
函数里面可以用local命令声明局部变量。
# 脚本 test.sh
fn () {
local foo
foo=1
echo "fn: foo = $foo"
}
fn
echo "global: foo = $foo"
上面脚本的运行结果如下。
fn: foo = 1
global: foo =
上面例子中,local命令声明的$foo变量,只在函数体内有效,函数体外没有定义。
2 算数运算
虽然bash脚本中变量都是字符串,但依旧可以通过一些方式执行一些基本的算数运算。
2.1 算术表达式
((...))语法可以进行整数的算术运算,比如下面这样:
((foo = 5 + 5))
echo $foo
结果为:

((...))会自动忽略内部的空格,所以下面的写法都正确,得到同样的结果。
((2+2))
(( 2+2 ))
(( 2 + 2 ))
这个语法不返回值,命令执行的结果根据算术运算的结果而定。只要算术结果不是0,命令就算执行成功。
(( 3 + 2 ))
echo $?
上面例子中,3 + 2的结果是5,命令就算执行成功,环境变量$?为0:

如果算术结果为0,命令就算执行失败。
(( 3 - 3 ))
echo $?
上面例子中,3 - 3的结果是0,环境变量$?为1,表示命令执行失败。
如果要读取算术运算的结果,需要在((...))前面加上美元符号$((...)),使其变成算术表达式,返回算术运算的值。
echo $((2 + 2))
((...))语法支持的算术运算符如下。
+:加法-:减法*:乘法/:除法(整除)%:余数**:指数++:自增运算(前缀或后缀)--:自减运算(前缀或后缀)
注意,除法运算符的返回结果总是整数,比如5除以2,得到的结果是2,而不是2.5:

++和--这两个运算符有前缀和后缀的区别。作为前缀是先运算后返回值,作为后缀是先返回值后运算,这和C/C++语言中的自增自减效果一致。
i=0
echo $i
echo $((i++))
echo $i
echo $((++i))
echo $i
上面例子中,++作为后缀是先返回值,执行echo命令,再进行自增运算;作为前缀则是先进行自增运算,再返回值执行echo命令:

$((...))内部可以用圆括号改变运算顺序。
echo $(( (2 + 3) * 4 ))
上面例子中,内部的圆括号让加法先于乘法执行。
$((...))结构可以嵌套。
echo $(((5**2) * 3))
# 等同于
echo $(($((5**2)) * 3))
这个语法只能计算整数,使用小数会报错:

$((...))的圆括号之中,不需要在变量名之前加上$,不过加上也不报错。
number=2
echo $(($number + 1))
上面例子中,变量number前面有没有美元符号,结果都是一样的。
如果在$((...))里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,因此不会报错。
echo $(( "hello" + 2))
echo $(( "hello" * 2))
上面例子中,"hello"会被当作变量名,返回空值,而$((...))会将空值当作0,所以乘法的运算结果就是0。同理,如果$((...))里面使用不存在的变量,也会当作0处理:

如果一个变量的值为字符串,跟上面的处理逻辑是一样的。即该字符串如果不对应已存在的变量,在$((...))里面会被当作空值。
foo=hello
echo $(( foo + 2))
上面例子中,变量foo的值是hello,而hello也会被看作变量名。这使得有可能写出动态替换的代码。
foo=hello
hello=3
echo $(( foo + 2 ))
上面代码中,foo + 2取决于变量hello的值:

最后,$[...]是以前的语法,也可以做整数运算,不建议使用。
echo $[2+2]
2.2 进制表达
Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。
number:没有任何特殊表示法的数字是十进制数(以10为底)。0number:八进制数。0xnumber:十六进制数。base#number:base进制的数。
下面是一些例子。
echo $((0xff))
echo $((2#11111111))
上面例子中,0xff是十六进制数,2#11111111是二进制数。
2.3 常用运算
首先$((...))支持以下的二进制位运算符。
<<:位左移运算,把一个数字的所有位向左移动指定的位。>>:位右移运算,把一个数字的所有位向右移动指定的位。&:位的“与”运算,对两个数字的所有位执行一个AND操作。|:位的“或”运算,对两个数字的所有位执行一个OR操作。~:位的“否”运算,对一个数字的所有位取反。^:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。
下面是右移运算符>>的例子。
echo $((16>>2))
下面是左移运算符<<的例子。
echo $((16<<2))
下面是17(二进制10001)和3(二进制11)的各种二进制运算:
echo $((17&3))
echo $((17|3))
echo $((17^3))
同时$((...))也支持以下的逻辑运算符。
<:小于>:大于<=:小于或相等>=:大于或相等==:相等!=:不相等&&:逻辑与||:逻辑或!:逻辑否expr1?expr2:expr3:三元条件运算符。若表达式expr1的计算结果为非零值(算术真),则执行表达式expr2,否则执行表达式expr3。
如果逻辑表达式为真,返回1,否则返回0。
echo $((3 > 2))
echo $(( (3 > 2) || (4 <= 1) ))
效果:

三元运算符执行一个单独的逻辑测试。它用起来类似于if/then/else语句。
a=0
echo $((a<1 ? 1 : 0))
上面例子中,第一个表达式为真时,返回第二个表达式的值,否则返回第三个表达式的值:

$((...))还可以执行赋值运算:
echo $((a=1))
echo $a
上面例子中,a=1对变量a进行赋值。这个式子本身也是一个表达式,返回值就是等号右边的值:

$((...))支持的赋值运算符,有以下这些。
parameter = value:简单赋值。parameter += value:等价于parameter = parameter + value。parameter -= value:等价于parameter = parameter – value。parameter *= value:等价于parameter = parameter * value。parameter /= value:等价于parameter = parameter / value。parameter %= value:等价于parameter = parameter % value。parameter <<= value:等价于parameter = parameter << value。parameter >>= value:等价于parameter = parameter >> value。parameter &= value:等价于parameter = parameter & value。parameter |= value:等价于parameter = parameter | value。parameter ^= value:等价于parameter = parameter ^ value。
下面是一个例子。
foo=5
echo $((foo*=2))
如果在表达式内部赋值,可以放在圆括号中,否则会报错。
echo $(( a<1 ? (a+=1) : (a-=1) ))