Shell 脚本变量详解:脚本中的“记忆容器”

引言:变量——脚本的记忆单元

想象一下,你要帮妈妈记录她的烘焙配方。你会拿出一张纸,写上:

  • 面粉 = 2杯
  • 糖 = 1杯
  • 烤箱温度 = 180度

这张纸记住了所有的配料和步骤,妈妈一看就明白。

在 Shell 脚本的世界里,变量就是这张“神奇的纸”。它是脚本的“记忆容器”,负责存储各种信息,比如名字、路径、数字,或者一条命令的执行结果。有了变量,脚本就能记住东西,变得更加聪明和灵活,能够处理复杂多变的任务。

本文将带你从零开始,全面了解 Shell 脚本中的变量,从如何“贴上标签”(定义变量)到如何“玩转标签”(高级应用),让你彻底掌握这个脚本编程的核心概念。


一、变量基础:贴标签与取东西

1.1 变量是什么?

在 Shell 中,变量本质上就是一个命名的存储空间。你可以把它想象成一个带标签的盒子:

  • 标签名就是变量名
  • 盒子里放的东西就是变量的值
    Shell 中的变量非常随和,它把所有东西(数字、文本、路径)都当作字符串来存放,不需要你提前声明它要放什么类型的数据。

1.2 如何给盒子贴标签?(命名规则)

给变量起名(贴标签)要遵循一些简单的规则:

  • 可以用的字符:字母(a-z, A-Z)、数字(0-9)、下划线(_)。
  • 不能做的事不能用数字开头。比如 1var 是错误的,而 var1 是正确的。
  • 🔠 大小写敏感MyVarmyvar 是两个完全不同的变量。
  • 💡 好习惯建议
    • 使用描述性的名字,比如用 backup_path 而不是 bp
    • 多个单词用下划线连接,如 student_name,这样更清晰。
    • 对于整个脚本都需要的重要变量(全局变量),通常用大写字母,如 CONFIG_FILE,这是一种约定俗成的规范。

1.3 如何往盒子里放东西?(定义与赋值)

把东西放进变量盒子(赋值)的语法非常简单:

name="Alice"       # 在名为‘name’的盒子里放入“Alice”这个值
count=10 # 在‘count’盒子里放入“10”(注意,即使是数字,也被当作字符串)
path="/home/logs" # 在‘path’盒子里放入一个路径字符串

⚠️ 千万注意!:等号 =两边不能有任何空格name = "Alice" 是错误的写法,Shell 会无法理解。


二、变量的作用域:私人物品与公共告示

变量不仅有自己的值,还有自己的“活动范围”,这就是作用域。

2.1 三种类型的变量

变量类型 作用范围 如何创建 例子 比喻
局部变量 当前脚本或Shell var=value my_temp="hi" 你日记本里的私密笔记
环境变量 当前及所有子进程 export var=value export PATH="/usr/bin" 小区公告栏的通知,人人都能看到
只读变量 无法修改 readonly var=value readonly PI=3.14 刻在石头上的字,无法更改

2.2 管理环境变量

环境变量就像系统的全局设置,非常重要。

  • 查看所有环境变量:使用 printenvenv 命令。
  • 让它永久生效:如果想让自己定义的环境变量每次登录都在,需要把 export VAR=value 这行代码写到你的 ~/.bashrc~/.bash_profile 文件里,然后执行 source ~/.bashrc 让它立刻生效。
  • 常用的系统环境变量
    echo "我是用户: $USER"    # 输出当前用户名
    echo "我家在: $HOME" # 输出当前用户的家目录路径
    echo "我目前在: $PWD" # 输出当前所在的工作目录路径

三、特殊变量:脚本的内置工具箱

Shell 提供了一些内置的特殊变量,非常有用,它们像是脚本自带的工具箱。

3.1 传递参数:和脚本对话

当你运行脚本时在后面加参数,比如 ./script.sh arg1 arg2,这些参数怎么在脚本里拿到呢?

特殊变量 含义与用途 示例
$0 脚本自己的名字 echo "正在运行: $0"
$1-$9 第1个到第9个参数 echo "第一个参数是: $1"
${10} 第10个参数(以此类推) echo "第十个参数: ${10}" 必须加花括号
$# 一共传了多少个参数 echo "你一共给了 $# 个参数"

3.2 $@$*:处理所有参数

它们都代表所有参数,但在细节上有微妙且重要的区别:

# 假设脚本接收三个参数:arg1 arg2 arg3

# “$*” 把所有参数合并成一个字符串:"arg1 arg2 arg3"
echo "Using \"\$*\": $*"

# “$@” 把每个参数都当作独立的字符串:"arg1" "arg2" "arg3"
echo "Using \"\$@\": $@"

# 在循环中,这种区别非常关键!
echo "Looping with \"\$*\":"
for item in "$*"; do
echo $item # 这只会循环一次,因为所有参数是一个整体
done

echo "Looping with \"\$@\":"
for item in "$@"; do
echo $item # 这会循环三次,分别输出每个参数
done

3.3 进程状态变量

变量 含义与用途 示例
$$ 当前脚本运行的进程ID(PID) echo "这个脚本的PID是: $$"
$! 最后一个在后台运行的进程PID echo "后台任务的PID是: $!"
$? 上一条命令的执行结果状态码 ls /tmp; echo "上条命令结果: $?"
0表示成功,非0表示出错

四、高级技巧:玩转你的变量

4.1 处理字符串

变量里存放的大部分是字符串,Shell 提供了一些方法来操作它们。

my_str="Hello World"

# 截取子字符串:${变量:起始位置:长度}
echo ${my_str:0:5} # 输出 "Hello" (从第0个字符开始,截取5个)

# 替换字符串:${变量/旧字符串/新字符串}
echo ${my_str/l/L} # 输出 "HeLlo World" (把第一个小写l换成大写L)
echo ${my_str//l/L} # 输出 "HeLLo WorLd" (把所有小写l都换成大写L)

# 转换大小写 (Bash 4.0+)
echo ${my_str^^} # 全部转大写 "HELLO WORLD"
echo ${my_str,,} # 全部转小写 "hello world"

4.2 设置默认值

处理可能不存在的变量时,这个功能非常实用。

# 如果变量 VAR 没设置或者为空,就使用后面的默认值,但不会改变 VAR 本身
result=${VAR:-"这是一个默认值"}

# 如果变量 VAR 没设置或者为空,不仅使用默认值,还会把这个默认值赋给 VAR
result=${VAR:="这个值会被赋给VAR"}

# 如果 VAR 未设置,就输出错误信息并退出脚本,用于强制参数校验
result=${VAR:?"错误!变量VAR没有定义,脚本无法执行!"}

4.3 进行数学计算

虽然变量存的是字符串,但我们仍然可以做数学计算。

a=10
b=3

# 方式一:$(( )) —— 最推荐,简洁高效
sum=$((a + b))
echo $sum # 输出 13

# 方式二:expr —— 老式方法,注意运算符两边要有空格
sum=`expr $a + $b` # 反引号包围命令
# 或者
sum=$(expr $a + $b) # 更现代的写法

# 方式三:let —— 用于计算并赋值
let "sum = a + b"

# 注意:Shell 默认只做整数运算。需要计算小数?请找‘bc’帮忙!
echo "scale=2; 3 / 2" | bc # 输出 1.50

五、实战演练:看看变量如何大显身手

5.1 案例一:自动生成带时间的日志文件名

#!/bin/bash

# 定义日志存放的目录
LOG_DIR="/var/log/my_app"

# 使用命令获取当前时间,并存入 TIMESTAMP 变量
TIMESTAMP=$(date +"%Y%m%d_%H%M%S") # 格式: 20231027_143022

# 组合变量,生成最终的日志文件名
LOG_FILE="${LOG_DIR}/app_${TIMESTAMP}.log"

# 使用变量
echo "程序开始运行,日志将记录到: $LOG_FILE"
# 实际应用中,可能会这样用:my_program >> $LOG_FILE

5.2 案例二:安全地读取用户输入

#!/bin/bash

# -p 提示用户,将输入读到 username 变量中
read -p "请输入您的用户名: " username

# -s silent模式,输入不回显(用于密码)
# -t 10 超时时间10秒
read -s -t 10 -p "请输入您的密码(10秒内): " password
echo # 换行

echo "欢迎您, $username!"
# 注意:$password 变量里现在存着用户输入的密码

5.3 案例三:检查脚本参数是否提供

#!/bin/bash

# 检查第一个参数是否为空(-z 判断字符串长度是否为0)
if [ -z "$1" ]; then
echo "错误:启动本脚本时,请提供一个配置文件路径作为参数!"
echo "用法: $0 <config-path>"
exit 1 # 非0退出表示脚本执行错误
fi

# 如果执行到这里,说明 $1 不为空
CONFIG_FILE="$1"
echo "开始使用配置文件: $CONFIG_FILE"
# ... 后续处理逻辑

六、最佳实践与避坑指南

  1. 引号的重要性

    • 赋值时不要加引号var=value
    • 使用变量时推荐加双引号echo "$var" — 这样可以避免变量值中有空格时被拆开,导致意外错误。
    • 值里有空格就必须加引号file_path="/path/with spaces/file.txt"
  2. 防止变量未定义的灾难
    在脚本开头加上 set -u。这样一旦你使用了未赋值的变量,脚本会立即报错并停止,而不是默默地继续执行下去。

    #!/bin/bash
    set -u # 开启“未定义变量报错”模式
    echo "$undefined_var" # 这行会导致脚本报错退出
    echo "这行不会被执行到"
  3. 理解数字的限制
    Shell 变量本质是字符串,所以数学计算有其局限性。记住:

    • 它主要进行整数运算
    • 需要复杂的小数计算时,请调用 bcawk 这些更强大的工具。

总结

变量是 Shell 脚本的基石和记忆核心。它从简单的存储和取值出发,通过作用域管理影响范围,通过特殊变量与脚本外部交互、感知自身状态,再通过字符串操作、默认值设置、数学运算等高级技巧变得无比强大。

从编写一个简单的日志脚本,到一个需要处理复杂用户输入和参数的系统工具,深刻理解并熟练运用变量是成功的关键。希望这篇详解能成为你 Shell 脚本之旅上的得力助手!