简介
编写脚本时,你需要管理数据的访问方式,以及函数如何向调用方报告成功或失败。
如果在两个不同的位置使用相同的变量名,其中一个变量可能会意外覆盖另一个变量。这是编程中常见的错误。我们可以使用作用域来解决这个问题,也就是将变量限制在函数内部。
此外,函数需要一种方式告诉主脚本自己执行成功还是失败。在 Bash 中,这通过返回状态码实现。
这项主题结合了大型脚本中非常重要的两个概念:防止变量泄漏到不应影响的位置,以及使用成功或失败状态控制执行流程。
在本实验中,你将学习:
- 变量作用域:使用
local防止变量冲突。 - 返回状态:使用退出代码(成功为 0,失败为非零值)控制脚本流程。
变量作用域:全局变量与局部变量
默认情况下,Bash 中的变量是全局变量。这意味着,如果你在函数内部定义一个变量,就可以在脚本的其他位置访问和修改它。这可能导致难以排查的问题:脚本的一部分意外修改了另一部分所需的数据。
为避免这种情况,我们使用 local 关键字。local 变量只存在于创建它的函数内部。
这是 Shell 脚本编程需要权衡的一点。Bash 让你可以快速开始,但这种便利也可能导致数据意外泄漏到整个脚本中。作用域规定了变量在哪些位置可见。
下面演示全局变量和局部变量的区别。
创建名为 scope.sh 的脚本:
cd ~/project
nano scope.sh
添加以下代码:
#!/bin/bash
## This is a Global variable
city="New York"
print_city() {
## This is a Local variable. It shares the name 'city', but it is a separate variable.
local city="London"
echo "Inside function: $city"
}
echo "Before function call: $city"
## Run the function
print_city
## Check if the Global variable was changed
echo "After function call: $city"
保存文件(按 Ctrl+O,再按回车键),然后退出(按 Ctrl+X)。
为脚本添加可执行权限并运行:
chmod +x scope.sh
./scope.sh
运行结果:
Before function call: New York
Inside function: London
After function call: New York
注意,函数外部的 city 变量仍然是「New York」。函数使用了自己的 city 局部副本(「London」),没有影响全局变量。
如果没有使用 local,函数就会覆盖全局变量,最后一行会输出「London」。
这就是脚本规模增大后 local 变得更加重要的原因。在简短的示例中,全局变量可能看起来没有问题。但在较长的维护脚本中,重复使用变量名可能产生细微且难以诊断的错误。
返回状态码
在许多编程语言中,函数会返回数据,例如字符串或数字。在 Bash 中,return 专门用于返回状态码,表示成功或失败。
- 0:成功。
- 1-255:失败(错误)。
不要将 return 和 echo 混淆。
echo将文本输出到屏幕,也可以在捕获输出时将文本保存到变量中。return向系统发送一个不可见的数字,用于表示「执行成功」或「执行失败」。
这个区别是 Shell 编程的基础。文本输出和状态报告是两条独立的通道。函数可以使用 echo 输出人类可读的信息,但 return 返回的值用于控制流程,而不是传递普通数据。
下面创建一个通过返回代码 5 来模拟失败的函数。
创建 status.sh:
nano ~/project/status.sh
添加以下代码:
#!/bin/bash
check_file() {
echo "Checking for file..."
## We simulate a specific error by returning Code 5
return 5
}
check_file
保存并退出。
为脚本添加可执行权限,然后运行:
chmod +x status.sh
./status.sh
输出:
Checking for file...
你可以看到文本输出,但数字 5 在哪里?它已经被静默返回了。下一步将学习如何捕获并使用这个返回值。
捕获返回代码($?)
要查看最近执行的命令或函数的返回代码,Bash 提供了一个特殊变量:$?。
当 check_file 完整执行后,$? 会自动保存值 5。
重要规则:必须在命令执行后立即读取 $?。一旦执行另一个命令(即使是简单的 echo),$? 就会更新为新命令的状态。
修改 status.sh,捕获返回代码。
打开文件:
nano status.sh
将脚本更新为:
#!/bin/bash
check_file() {
echo "Checking for file..."
return 5
}
check_file
## Capture the return code immediately into a variable
ret_val=$?
echo "The function returned: $ret_val"
保存并退出。再次运行:
./status.sh
输出:
Checking for file...
The function returned: 5
现在,错误代码已经被捕获到变量 ret_val 中。
捕获返回代码后,脚本的其他部分就可以根据它做出合理的响应。后续的 if 语句可以检查这个值,并决定继续执行、重试还是停止。这样,多个函数就能在大型脚本中相互协作,而不是各自作为孤立的代码片段运行。
根据返回状态执行操作
现在已经获得状态码,可以使用 if 语句妥善处理错误。
- 如果代码是
0,表示一切正常。 - 如果代码是
5,就能准确知道发生了什么问题,例如「文件缺失」。
Linux 标准命令也是这样工作的。例如,grep 找到匹配项时返回 0,找不到匹配项时返回 1。
更新 status.sh,加入处理逻辑:
nano status.sh
添加条件判断:
#!/bin/bash
check_file() {
echo "Checking for file..."
return 5
}
check_file
ret_val=$?
## Logic based on the return code
if [ $ret_val -eq 0 ]; then
echo "Success: File found."
elif [ $ret_val -eq 5 ]; then
echo "Error: File missing (Code 5)."
else
echo "Unknown Error: Code $ret_val"
fi
保存并退出。运行脚本:
./status.sh
输出:
Checking for file...
Error: File missing (Code 5).
现在,你已经实现了基本的错误处理。如果文件检查失败,脚本就能识别该情况,并输出具体的错误信息,而不是崩溃或盲目继续执行。
总结
在本实验中,你练习了两个重要的脚本编程概念:
- 变量作用域(
local):使用local可以确保函数内部的变量不会覆盖全局变量,使脚本行为更加可预测。 - 返回状态(
return、$?):使用退出代码可以让函数向主脚本报告成功或失败,从而编写能够智能处理错误的脚本。
除非确实需要修改全局变量,否则应始终在函数内部使用 local 声明变量。



