Bash 函数
Bash 函数
Bash 函数和 Bash 脚本的各个方面都非常相似,其底层可能是同一个东西。
简介
函数(function)是可以重复使用的代码片段,有利于代码的复用。它与别名(alias)的区别是,别名只适合封装简单的单个命令,函数则可以封装复杂的多行命令。
我们可以将带有参数的命令设置一个别名,alias,所以别名也可以做简单的函数,比如查找特定文件之类的
函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。
如果函数与脚本同名,函数会优先执行。但是,函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。
shell 环境下同名的时候的优先级:别名 > 函数 > 脚本
Bash 函数定义的语法有两种。
# 第一种
fn() {
# codes
}
# 第二种
function fn() {
# codes
}
上面代码中,fn
是自定义的函数名,括号里是不写参数的,函数代码就写在大括号之中。上面这两种写法是等价的。不过一般推荐第二种,带有 function 关键字,可读性好一些
我们在交互式 shell 中创建函数的时候,输入完
function test_fun() {
之后直接回车,不会直接执行,而是像 Here 文档一样,出现>
,直到输入}
,关于 Here 文档,请看《转义和引号》
删除一个函数,可以使用 unset
命令。关于 unset 命令,请看《Bash 变量》的 删除变量
小节
unset -f functionName
查看当前 Shell 已经定义的所有函数,可以使用 declare
命令。关于 declare 命令,请看《Bash 变量》的 declare 命令
小节
$ declare -f
上面的 declare
命令不仅会输出函数名,还会输出所有定义。输出顺序是按照函数名的字母表顺序。由于会输出很多内容,最好通过管道命令配合 more
或 less
使用。
declare
命令还支持查看单个函数的定义。
$ declare -f functionName
declare -F
可以输出所有已经定义的函数名,不含函数体。
$ declare -
简单实践如下:
$ function test_fun(){
> echo "hello from test_fun"
> }
$ declare -f
test_fun ()
{
echo "hello from test_fun"
}
$ declare -F
declare -f test_fun
$ unset -f test_fun
$ declare -F
$ declare -fdeclare 命令
调用时,就直接写函数名,参数跟在函数名后面。这种传参的方式跟脚本传参的默认方式一样
函数体里面的 $1
表示函数调用时的第一个参数。其他几个变量比如 $@
、$#
,其含义也跟在脚本中一样
简单实践如下:
$ function test_echo(){
> echo params is $@
> }
$ test_echo 111 222 333
params is 111 222 333
参数变量
函数体内可以使用参数变量,获取函数参数。函数的参数变量,与脚本参数变量是一致的。
$1
~$9
:函数的第一个到第 9 个的参数。$0
:函数所在的脚本名。$#
:函数的参数总数。$@
:函数的全部参数,参数之间使用空格分隔。$*
:函数的全部参数,参数之间使用变量$IFS
值的第一个字符分隔,默认为空格,但是可以自定义。
如果函数的参数多于 9 个,那么第 10 个参数可以用 ${10}
的形式引用,以此类推。
此外注意,脚本传参是做不到传递数组变量的,只能做到将所有的数组元素挨个传入,然后再在脚本中组合成一个数组。
shift
命令可以改变脚本参数,每次执行都会移除脚本当前的第一个参数($1
),使得后面的参数向前一位,即 $2
变成 $1
、$3
变成 $2
、$4
变成 $3
,以此类推。
shift
命令可以接受一个整数作为参数,指定所要移除的参数个数,默认为 1
。
此外,在方法中也可以使用 getopts 命令或者 getopt 命令,以 -
或者 --
的格式传入参数,跟脚本的传参方式基本无异。而且调用起来比脚本调用起来更加简洁
具体请看《Bash 脚本》的
脚本参数
小节。
简单实践如下:
创建 function.sh
#!/usr/bin/env bash
# 脚本日志文件
log_file_path="$(pwd)/logs.txt"
# 若日志不存在,则创建日志文件
if [ -e "$log_file_path" ];then
touch $log_file_path
fi
# 清空日志文件
> $log_file_path
# 永久地将错误输出重定向到标准输出
exec 2>&1
# 永久地将标准输出重定向到日志文件中
exec 1> $log_file_path
# 记录日志的方法
function log_message(){
echo $(date '+%Y-%m-%d %H:%M:%S') $0: $@
}
# 测试 shift
function params_manip(){
for params in $@;do
log_message $1
shift
done
}
# 调用方法
params_manip 11 222 333 44 55
# 测试 getopts 解析参数
function test_func_params(){
while getopts 'sn:v:' OPTION; do
case "$OPTION" in
s)
echo "switch on"
;;
n)
name="$OPTARG"
echo "name is $name"
;;
v)
value="$OPTARG"
echo "value is $value"
;;
?)
echo "script usage: $(basename $0) [-s] [-n somevalue] [-v somevalue]" >&2
;;
esac
done
}
# 调用方法
test_func_params -s -n xiashuo -v 10
赋权后执行
$ ./function.sh && cat logs.txt
2023-09-08 14:47:51 ./function.sh: 11
2023-09-08 14:47:51 ./function.sh: 222
2023-09-08 14:47:51 ./function.sh: 333
2023-09-08 14:47:51 ./function.sh: 44
2023-09-08 14:47:51 ./function.sh: 55
switch on
name is xiashuo
value is 10
return 命令
return
命令用于从函数返回一个退出码,这个退出码的范围为 0-255。函数执行到这条命令,就不再往下执行了,直接返回了。
function func_return_value {
return 10
}
函数将退出值返回给调用者。如果命令行直接执行函数,下一个命令可以用 $?
拿到退出码的值。
$ func_return_value
$ echo "Value returned by function is: $?"
Value returned by function is: 10
return
后面不跟参数,只用于返回也是可以的。此时会返回函数体中最后一个命令的退出码。
function name {
commands
return
}
具体请看《Bash 脚本》的
return 命令
小节
如果函数想返回除退出码以外的内容,则可以在函数体中通过 echo 语句中输出,然后用子命令拓展符号将调用方法的语句包起来,例如 $(func param1 param2)
,这样就可以的获取函数中所有的 echo 语句返回的值,默认以 IFS 变量分割,甚至还可以通过 ()
包起来,成为数组。
关于子命令拓展,请看《Bash 的模式拓展》的
子命令拓展
小节关于数组,请看《Bash 数组》小节
简单实践如下:
$ function test_return(){
> echo 111
> echo 222
> echo 333
> }
$ value=$(test_return)
$ echo $value
111 222 333
$ value_arr=($(test_return))
$ echo ${value_arr[@]}
111 222 333
$ echo ${#value_arr[@]}
3
传递参数到子脚本和方法中的时候无法传递数组类型,只能传递字符串类型,其实也就证明 Bash 中不存在单独的数组类型,只有字符串一个类型。
既然 Bash 没有数据结构,没有原始数据类型和对象类型这些东西,自然也就不存在什么值传递和引用传递的区别,
而这,这是 bash 的局限性。
全局变量和局部变量
Bash 函数体内直接声明的变量,属于全局变量,(在同一脚本中)方法外,甚至别的方法内部都可以读取。
而且函数体内不仅可以声明全局变量,还可以修改全局变量。
函数里面可以用 local
命令声明局部变量。方法中的局部变量只在方法内部可以使用。
简单实践如下:
创建 function_var.sh
#!/usr/bin/env bash
name="aaa"
function change_var(){
name="bbb"
age=12
}
function change_var2(){
age=22
local gender="male"
}
change_var
change_var2
echo name: $name
echo age: $age
echo gender: $gender
赋权后执行:
$ ./function_var.sh
name: bbb
age: 22
gender:
local
命令声明的 $gender
变量,只在函数体内有效,函数体外没有定义。
再次感觉方法调用起来比脚本调用起来更加简洁