编写健壮且可移植脚本的综合指南
立即解锁
发布时间: 2025-08-24 01:19:56 阅读量: 1 订阅数: 4 

# 编写健壮且可移植脚本的综合指南
## 1. 引言
编写良好的可移植脚本不仅需要掌握外壳的技术细节,还需具备额外的技能。由于无法在所有系统上进行成功测试,脚本可能会在编写时不存在的系统上运行,新的标准和扩展不断涌现,同时也会有新的错误潜入生产版本。因此,如何编写能适应新系统的脚本至关重要。
## 2. 脚本的健壮性
### 2.1 健壮性的定义
一个程序在遇到意外故障时仍能正常工作,就被称为健壮的程序。健壮性在多个层面都很有用,它能使程序在小问题出现时继续运行,在大问题发生时提供有用的诊断信息,还能检测和纠正错误、处理意外情况,并在工作环境发生变化时存活下来。
### 2.2 可移植代码中的健壮性
在可移植代码中,健壮性更为重要,因为可能出现意外错误的情况更多。程序可能会提供同一实用工具的多个不兼容版本,如果通过路径指定特定版本,代码可能无法在其他系统上正常运行。
### 2.3 深度防御策略
计算机安全和可靠性专家常提倡采用深度防御策略,即设置多层冗余保护来抵御错误。在设计代码时,应在多个点检测和防范错误,验证文件名的有效性,检查操作是否成功。即使是错误处理代码也可能存在错误,因此要尽早并持续测试假设,对值进行合理性检查。例如,如果认为获取到了文件的绝对路径,那么该路径中最好包含路径分隔符,否则可能出现了问题。
## 3. 处理失败
### 3.1 健壮代码的核心
健壮代码的核心在于处理失败。无法确保任何事情都不会失败,所能做的就是检查失败并进行处理。处理错误并不一定意味着纠正错误,有时只能诊断出问题所在,并在情况恶化之前终止执行。
### 3.2 处理并非总是纠正
在某些情况下,可以纠正错误;但在另一些情况下,无法纠正错误。此时,应发出诊断信息说明问题所在,进行清理并终止执行。通常,在出现问题后继续执行很少有益,但在某些情况下也可行。一般规则是,如果未来的操作不依赖于先前的操作,可以尝试所有操作,并报告失败的操作;如果操作是按逻辑顺序进行的,一旦出现问题就应终止执行。
### 3.3 无法处理则不检查
在处理失败时,最重要的原则是:如果对某个错误无能为力,检查它就毫无用处。这并不意味着不检查无法完全纠正的错误,而是指那些无法采取任何措施的错误。例如,以下代码片段:
```bash
func_die() {
if echo "$@" >&2
then :
else # what goes here?
fi
exit 1
}
```
该函数试图显示一条消息,然后以失败状态退出,类似于标准的Perl函数`die`。它会测试`echo`命令是否成功,如果不成功,就会陷入困境。如果无法向标准错误输出写入内容,就无法显示一条消息说明无法向标准错误输出写入内容。脚本本来就要以异常退出码退出,因此无法用它来传达出现了严重问题。虽然无法显示错误消息可能是个问题,但这是无法解决的问题。
此外,这个函数还存在另一个潜在问题。如果脚本在出现问题后需要进行清理,但清理代码可能无法运行,从而导致临时文件或其他对象处于不确定状态,可能会使系统混乱或在未来运行时引发错误。如果有清理代码,应在调用任何旨在退出脚本的函数之前运行它(或者使用`trap cleanup_code 0`)。
### 3.4 避免错误连锁反应
在脚本编程中,常见的灾难往往是在一段代码失败后,后续代码仍继续运行。例如:
```bash
for i in $names
do
mkdir $i
cd $i
[... do stuff ...]
cd ..
rm -rf $i
done
```
如果`$names`中的某个名称是`.`(当前目录)、包含空格或其他奇怪字符,这段代码可能会导致灾难性的后果。当`$names`中有`.`时,`mkdir .`会失败,但`cd .`会成功,后续操作可能会导致意外结果,最终`rm -rf .`会删除新的工作目录。
为了避免这种情况,可以添加基本的错误检查:
```bash
for i in $names
do
mkdir $i || continue
( cd $i || exit
[... do stuff ...]
)
rm -rf $i
done
```
这样,如果初始的`mkdir`失败,就不会执行任何操作。将`cd`命令放在子shell中可以确保子shell之后的命令仍在起始目录中,无论子shell中发生什么。
### 3.5 错误检查的重要性
不要省略错误检查。为了提高可读性和简洁性,很多示例中会省略大量的错误检查,但在生产代码中应更加谨慎。可以考虑使用`set -e`选项,使外壳在出现错误时终止执行。但不建议使用该选项,因为它容易忽略真正无害的边界条件,并且外壳不提供诊断信息,难以确定问题所在。例如:
```bash
diff -u file.old file.new > file.diffs
```
`diff`命令在将两个文件的差异写入标准输出的同时,如果遇到差异会返回非零状态。如果不关心命令的退出状态,可以使用`|| true`确保命令整体返回成功状态:
```bash
diff -u file.old file.new > file.diffs || true
```
## 4. 临时文件和清理
### 4.1 清理的不确定性
使用`trap`命令处理临时文件的清
0
0
复制全文
相关推荐










