首页 Shell 函数
文章
取消

Shell 函数

定义与使用

在 shell 中,同样有函数的概念,具体语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 写法1 (推荐, 可移植性强)
func_name() {
    command1
    command2
    ...
    commandN
    return exit_code
}

# 写法2
function func_name {
    command1
    command2
    ...
    commandN
    return exit_code
}
  • exit_code函数的退出状态码,类似 命令的退出状态码,范围[0,255]0成功,非0失败。
  • 如果省略return语句,则函数中执行的最后一条命令的退出状态码将被作为函数的退出状态码

定义好函数之后,我们就可以使用它了。

使用函数很简单,一个函数就和一个命令一样,只需要写函数名,不用加()

func_name;还可以传递参数func_name arg1 arg2 arg3,在函数内使用$n访问参数;

  • $0为当前可执行文件名,$1为第一个参数、$2为第二个参数,…,以此类推;
  • $#获取参数个数,$@$*获取所有参数,$?获取前一个命令/函数的退出状态码。
  • $@$*的区别在 位置参数 讲过;你会发现 shell 函数和 shell 脚本在某些方面很相似。

递归调用

与其他编程语言一样,shell 函数也支持 递归调用,即自己调用自己。

函数局部变量

函数中定义的变量默认是全局变量,函数返回后,外部仍然可以访问对应变量。

使用关键字local可以定义局部变量,如local name1=value1 name2=value2 ...

局部变量的作用域生命周期被限定在所在函数的执行期间,函数返回后被自动销毁。

若局部变量和全局变量同名,则优先使用局部的。请养成使用 local 的习惯,减少全局污染。


注意,如果在 local 的同时赋值,且赋值过程中涉及到函数或命令调用,则退出状态码$?会丢失。

如果不希望发生该情况,请将变量声明和赋值/初始化分开,如 local res err; res="$(foo)"; err=$?


bash 在遇到 $var 等语句时,查找变量的顺序依次是:

  • 当前函数声明的 local 变量
  • 外层函数声明的 local 变量(按照调用顺序,依次往上查找)
  • 全局变量、环境变量

只要找到其中一个,就停止查找。

例如:只要声明 local 变量的那个函数还在执行,位于调用栈更深处的函数也能 访问/修改 对应 local 变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

bar() {
    echo "[bar-1] a=$a b=$b c=$c x=$x" # a=main_a b=main_b c=foo_c x=
    a=bar_c
    x=global_x
    echo "[bar-2] a=$a b=$b c=$c x=$x" # a=bar_c b=main_b c=foo_c x=global_x
}

foo() {
    local c=foo_c
    echo "[foo-1] a=$a b=$b c=$c x=$x" # a=main_a b=main_b c=foo_c x=
    bar
    echo "[foo-2] a=$a b=$b c=$c x=$x" # a=bar_c b=main_b c=foo_c x=global_x
}

main() {
    local a=main_a b=main_b c=main_c
    echo "[main-1] a=$a b=$b c=$c x=$x" # a=main_a b=main_b c=main_c x=
    foo
    echo "[main-2] a=$a b=$b c=$c x=$x" # a=bar_c b=main_b c=main_c x=global_x
}

main
echo "a=$a b=$b c=$c x=$x" # a= b= c= x=global_x
1
2
3
4
5
6
7
8
$ ./a.sh
[main-1] a=main_a b=main_b c=main_c x=
[foo-1] a=main_a b=main_b c=foo_c x=
[bar-1] a=main_a b=main_b c=foo_c x=
[bar-2] a=bar_c b=main_b c=foo_c x=global_x
[foo-2] a=bar_c b=main_b c=foo_c x=global_x
[main-2] a=bar_c b=main_b c=main_c x=global_x
a= b= c= x=global_x

命令分组

  • { command1; command2; ...; commandN; }:命令分组,在当前shell进程执行。
  • ( command1; command2; ...; commandN; ):命令分组,在fork出的子进程执行。

与函数不同,在命令分组中,你不能定义 local 变量,也不能使用 return 语句。

  • 对于{...},如果花括号与命令在同一行,则{后必须要有空白符,最后一个命令必须;结尾。
  • 对于(...),则没有此要求,(与第一个语句之间不需要空白符,最后一个命令也不必;结尾。

{ echo www.zfl9.com; },如果不在同一行,就不需要用空格隔开和分号结束,具体例子:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

# 正确
{ echo www.zfl9.com;}

# 正确
{ echo www.zfl9.com; }

# 正确
{
    echo www.zfl9.com
}

另一种函数定义

  • func_name() { ... }:定义一个函数,函数在 当前shell进程 执行。
  • func_name() ( ... ):定义一个“子程序”,子程序在 fork出来的shell进程 执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

func1() {
    echo "[func1] change dir to /etc"
    cd /etc
    echo "[func1] current dir is: $(pwd)"
}

func2() (
    echo "[func2] change dir to /bin"
    cd /bin
    echo "[func2] current dir is: $(pwd)"
)

cd /

echo "[main] before call func1: $(pwd)"
func1
echo "[main] after call func1: $(pwd)"

echo "[main] before call func2: $(pwd)"
func2
echo "[main] after call func2: $(pwd)"
1
2
3
4
5
6
7
8
9
10
11
$ chmod +x test.sh

$ ./test.sh
[main] before call func1: /
[func1] change dir to /etc
[func1] current dir is: /etc
[main] after call func1: /etc
[main] before call func2: /etc
[func2] change dir to /bin
[func2] current dir is: /bin
[main] after call func2: /etc

func_name() { ... } 定义的是一个函数,函数体在当前 shell 进程中执行,所以函数内使用 cd 改变工作目录时,会影响其所在的 shell 进程状态;

func_name() ( ... ) 定义的是一个“子程序”,调用它时,会先 fork 出一个子进程,在子进程中执行对应 shell 代码,子进程与调用方进程互不影响。

在某些场合,使用 func_name() ( ... ) 有奇效。

本文由作者按照 CC BY 4.0 进行授权

Shell IFS 变量

Shell 重定向