引子
最近帮团队面试了几位候选人,发现很多人在Shell脚本基础问题上栽了跟头。这些题目看似简单,却在日常开发中频繁使用,一旦理解不透彻就容易埋下隐患。今天就来聊聊这些面试中常见但容易被忽略的Shell脚本问题。
变量作用域的那些坑
子Shell中的变量传递
#!/bin/bash
count=0
echo "初始值: $count"
# 错误示范
count=10 | echo "管道中的值: $count"
echo "管道后的值: $count"
# 正确做法
count=20
echo "直接赋值后的值: $count"
# 使用命令替换
count=$(echo 30)
echo "命令替换后的值: $count"
关键点: 管道会创建子Shell,子Shell中的变量修改不会影响父Shell。这是很多脚本Bug的根源。
环境变量的正确使用
#!/bin/bash
# 错误:只在当前进程生效
MY_VAR="hello"
./another_script.sh
# 正确:传递给子进程
export MY_VAR="hello"
./another_script.sh
# 或者临时设置环境变量
MY_VAR="hello" ./another_script.sh
字符串处理的进阶技巧
参数扩展的高级用法
#!/bin/bash
filename="/path/to/somefile.txt"
# 获取文件名(不带路径)
echo "${filename##*/}"
# 获取目录路径
echo "${filename%/*}"
# 获取文件扩展名
echo "${filename##*.}"
# 变量默认值设置
unset maybe_empty
echo "${maybe_empty:-默认值}"
实战场景: 在处理文件路径、配置项时,这些参数扩展技巧能让代码更简洁健壮。
条件判断的细节差异
单中括号 vs 双中括号
#!/bin/bash
str="hello world"
# 单中括号 - 更传统,需要转义
if [ "$str" = "hello world" ]; then
echo "匹配成功"
fi
# 双中括号 - 更现代,支持更多特性
if [[ $str == "hello world" ]]; then
echo "匹配成功"
fi
# 模式匹配(只在双中括号中支持)
if [[ $str == h* ]]; then
echo "以h开头"
fi
数值比较的特殊性
#!/bin/bash
num1=10
num2=20
# 错误:使用字符串比较符号
if [ "$num1" > "$num2" ]; then
echo "错误比较"
fi
# 正确:使用数值比较符号
if [ "$num1" -gt "$num2" ]; then
echo "num1 大于 num2"
else
echo "num1 不大于 num2"
fi
错误处理的实战经验
立即退出与错误捕获
#!/bin/bash
# 任何命令失败立即退出
set -e
# 显示执行的命令(调试时很有用)
set -x
# 捕获未定义变量
set -u
# 组合使用
set -euxo pipefail
# 临时禁用错误退出
set +e
some_might_fail_command
set -e
信号处理与资源清理
#!/bin/bash
cleanup() {
echo "正在清理临时文件..."
rm -f /tmp/temp_file.*
echo "清理完成"
}
# 注册信号处理函数
trap cleanup EXIT INT TERM
# 业务逻辑
echo "创建临时文件..."
touch /tmp/temp_file.$$
# 模拟长时间运行
sleep 10
函数返回值的隐藏规则
返回值与退出状态码
#!/bin/bash
# 函数返回字符串(错误做法)
get_message() {
echo "Hello World"
}
result=$(get_message)
echo "结果: $result"
# 检查函数执行状态
if get_message > /dev/null; then
echo "函数执行成功"
else
echo "函数执行失败"
fi
# 正确设置退出状态码
check_file() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo "文件不存在: $file"
return 1
fi
return 0
}
数组操作的实用技巧
数组遍历与操作
#!/bin/bash
# 数组定义
fruits=("apple" "banana" "orange")
# 遍历数组(索引方式)
for ((i=0; i<${#fruits[@]}; i++)); do
echo "水果 $i: ${fruits[i]}"
done
# 遍历数组(值方式)
for fruit in "${fruits[@]}"; do
echo "水果: $fruit"
done
# 数组切片
echo "前两个水果: ${fruits[@]:0:2}"
# 向数组添加元素
fruits+=("grape")
实战案例:安全的文件处理脚本
#!/bin/bash
set -euo pipefail
process_files() {
local directory="${1:-.}"
# 检查目录是否存在
if [[ ! -d "$directory" ]]; then
echo "错误: 目录不存在: $directory" >&2
return 1
fi
# 安全地处理文件
while IFS= read -r -d '' file; do
if [[ -f "$file" && -r "$file" ]]; then
echo "处理文件: $file"
# 实际处理逻辑
process_single_file "$file"
fi
done < <(find "$directory" -type f -print0)
}
process_single_file() {
local file="$1"
# 在这里实现具体的文件处理逻辑
echo "正在处理: $file"
}
# 主程序
main() {
local target_dir="${1:-}"
if [[ -z "$target_dir" ]]; then
echo "用法: $0 <目录>" >&2
return 1
fi
process_files "$target_dir"
}
# 脚本入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
总结思考
通过这些面试问题,我深刻体会到:Shell脚本的功力不在于会写多复杂的逻辑,而在于对基础概念的深刻理解和细节的精准把握。在实际工作中,那些看似简单的语法细节,往往决定了脚本的稳定性和可维护性。
希望这些经验对你有帮助,下次面试或者写脚本时,能够避开这些常见的陷阱。
暂无评论