变量作用域

Shell 中的变量默认是全局的,在脚本的任何地方都能访问。这在函数里可能会带来问题——函数里的变量可能意外修改外部的变量。local 关键字可以解决这个问题。

全局变量

默认情况下,Shell 变量都是全局的:

#!/bin/bash

name="外部变量"

show_name() {
    echo "函数内:$name"
    name="函数内修改"
}

echo "调用前:$name"
show_name
echo "调用后:$name"

运行结果:

调用前:外部变量
函数内:外部变量
调用后:函数内修改

函数里对 name 的修改影响到了外部的变量。如果只是想在函数里用个临时变量,这就会出问题。

局部变量

用 local 关键字声明的变量是局部的,只在当前函数内有效:

#!/bin/bash

name="外部变量"

show_name() {
    local name="局部变量"
    echo "函数内:$name"
}

echo "调用前:$name"
show_name
echo "调用后:$name"

运行结果:

调用前:外部变量
函数内:局部变量
调用后:外部变量

函数里的 name 是局部变量,和外部的 name 互不影响。养成好习惯,函数里的临时变量都用 local 声明。

局部变量的作用域

局部变量只在定义它的函数及其子函数中有效:

outer() {
    local x=10
    echo "outer: $x"
    inner
    echo "outer after inner: $x"
}

inner() {
    echo "inner: $x"
    x=20
}

outer

运行结果:

outer: 10
inner: 10
outer after inner: 20

inner 函数能访问 outer 的局部变量 x,而且修改会影响 outer。这是因为 inner 是在 outer 内部调用的。

如果 inner 是独立定义的,情况就不一样了:

#!/bin/bash

inner() {
    local x=30
    echo "inner: $x"
}

outer() {
    local x=10
    echo "outer: $x"
    inner
    echo "outer after inner: $x"
}

outer

运行结果:

outer: 10
inner: 30
outer after inner: 10

inner 自己声明了 local x,和 outer 的 x 是两个不同的变量。

函数嵌套时的变量

func_a() {
    local var_a="A的变量"
    echo "func_a: $var_a"
    func_b
    echo "func_a 调用后: $var_a"
}

func_b() {
    echo "func_b 尝试访问: $var_a"
    var_a="B修改了"
}

func_a

运行结果:

func_a: A的变量
func_b 尝试访问: A的变量
func_b 可以访问并修改
func_a 调用后: B修改了

func_b 没有用 local 声明 var_a,所以它访问的是 func_a 的局部变量。这是个容易踩的坑。

最佳实践

函数内的变量都用 local:除非确实需要修改外部变量,否则一律用 local。

calculate() {
    local sum=0
    local i
    for i in "$@"; do
        sum=$((sum + i))
    done
    echo $sum
}

需要共享的变量用明确的名字:如果多个函数需要共享某个变量,给它一个清晰的名字,并在注释中说明。

# 全局变量:记录错误数量
ERROR_COUNT=0

report_error() {
    echo "错误:$1"
    ERROR_COUNT=$((ERROR_COUNT + 1))
}

避免变量名冲突:局部变量可以用简短的名字,全局变量最好加上前缀。

#!/bin/bash

MYAPP_DEBUG=0
MYAPP_LOG_FILE="/var/log/myapp.log"

process() {
    local debug=$MYAPP_DEBUG
    local file=$MYAPP_LOG_FILE
    # ...
}

小结

  • Shell 变量默认是全局的,函数内修改会影响外部
  • local 声明的变量是局部的,只在当前函数有效
  • 子函数可以访问父函数的局部变量
  • 养成习惯:函数内的临时变量都用 local 声明
  • 全局变量加前缀,避免命名冲突