Bash 变量命名规则与类型使用
一、变量命名
Bash 变量名由 POSIX 可移植字符集 限制,底层由 isname() 函数判断(man bash → "Name" 章节)。
1. 合法字符集
| 字符类型 |
是否允许 |
示例 |
字母 a-z A-Z |
是 |
var, HOME |
数字 0-9 |
是(但不能首字符) |
count1, PORT_8080 |
下划线 _ |
是(可首字符) |
_hidden, __internal__ |
连字符 - |
否 |
my-var → 语法错误 |
点 . |
否(会被解析为命令或路径) |
app.config → 错误 |
美元 $ |
否(变量引用符) |
$var 是引用,不是命名 |
| 空格 / 制表符 |
否 |
my var → 两个 token |
其他符号 @ * ? ! 等 |
否 |
path@dir → 错误 |
底层实现 :变量名匹配正则 ^[a-zA-Z_][a-zA-Z0-9_]*$
可通过 declare -p 查看变量属性验证。
2. 首字符严格限制
valid_var=1 # ✅ 字母开头
_valid=1 # ✅ 下划线开头(常用于"私有")
1invalid=1 # ❌ 数字开头 → 语法错误
3. 大小写敏感(独立命名空间)
VAR=10
var=20
echo $VAR # → 10
echo $var # → 20
注意 :某些系统保留大写变量(如 PATH),建议小写用于自定义局部变量。
4. 禁止使用 Bash 保留字(完整列表)
| 类别 |
保留字 |
| 控制结构 |
if, then, else, elif, fi, case, esac, for, while, until, do, done |
| 声明 |
declare, typeset, local, export, readonly |
| 其他 |
function, select, time, {, }, [[, ]], ((, )) |
if=1 # 语法错误
local=1 # 语法错误
可通过 compgen -k 查看所有保留字。
5. 变量名长度限制
- 理论无限制(受内存限制)
- 实际建议:≤ 32 字符(可读性)
- 过长会导致
bash: warning: command substitution: ignored null byte 等边缘问题
二、变量类型深度解析(定义、生命周期、继承、销毁)
类型 1:普通变量(User-Defined Variable)
| 项目 |
说明 |
| 定义 |
name=value(等号两边不能有空格) |
| 作用域 |
当前 Shell 进程 |
| 继承 |
不被子进程继承 |
| 生命周期 |
Shell 退出前存在 |
| 是否可修改 |
是 |
config_file="/etc/app.conf"
temp_dir="/tmp/app_$$" # $$ 是当前 PID
常见错误
config file = "a.conf" # 错误:被解析为命令 `config`
config_file = "a.conf" # 错误:等号两边有空格
类型 2:环境变量(Environment Variable)
| 项目 |
说明 |
| 定义 |
export NAME=value 或 NAME=value; export NAME |
| 作用域 |
当前进程 + 所有子进程 |
| 继承 |
通过 env 传递给子 Shell/程序 |
| 命名惯例 |
全大写 + 下划线(POSIX 约定) |
| 查看 |
printenv, env, `set |
export JAVA_HOME="/usr/lib/jvm/java-17"
export PATH="$PATH:$JAVA_HOME/bin"
继承机制详解
# parent.sh
export GREETING="Hello"
bash child.sh # child.sh 可直接使用 $GREETING
取消导出
export MYVAR=1
unset MYVAR # 完全删除
export -n MYVAR # 仅取消导出(变量仍存在)
系统预定义环境变量(部分)
| 变量 |
含义 |
示例 |
PATH |
可执行文件搜索路径 |
/usr/bin:/bin |
HOME |
用户主目录 |
/home/user |
PWD |
当前工作目录 |
/tmp |
OLDPWD |
上一个目录 |
/var |
SHELL |
当前 Shell |
/bin/bash |
USER/LOGNAME |
当前用户 |
alice |
UID |
用户 ID |
1000 |
LANG/LC_ALL |
语言环境 |
ja_JP.UTF-8 |
类型 3:位置变量 & 特殊参数(Positional & Special Parameters)
| 参数 |
含义 |
是否可修改 |
示例 |
$0 |
脚本文件名(含路径) |
否 |
./script.sh |
$1--$9 |
第 1--9 个参数 |
否 |
arg1 |
${10} |
第 10 个及以上参数(必须用 {}) |
否 |
arg10 |
$# |
参数总数 |
否 |
3 |
$@ |
所有参数(推荐,保留分隔) |
否 |
"a" "b" "c" |
$* |
所有参数(合并为一个字符串) |
否 |
a b c |
$$ |
当前 Shell PID |
否 |
12345 |
$! |
最后后台进程 PID |
否 |
12346 |
$? |
上个命令退出码(0=成功) |
否 |
0 |
$- |
当前选项标志(如 himBHs) |
否 |
himBH |
$@ vs $* 关键区别
set -- "a b" "c" "d e"
echo "=== $* ==="
for i in $*; do echo $i; done
# 输出:
# a
# b
# c
# d
# e
echo "=== $@ ==="
for i in $@; do echo $i; done # 错误!与 $* 相同
for i in "$@"; do echo $i; done # 正确!保留原始分隔
# 输出:
# a b
# c
# d e
最佳实践 :永远使用 "$@" 传递参数。
移位操作 shift
shift # 丢弃 $1,$2 → $1,$3 → $2
shift 2 # 丢弃前两个参数
类型 4:只读变量(Readonly / Constant)
| 项目 |
说明 |
| 定义 |
readonly NAME=value 或 declare -r NAME=value |
| 作用域 |
当前 Shell |
| 继承 |
值被继承,但非只读(子进程可修改) |
| 查看 |
readonly -p |
readonly BUILD_DATE=$(date -u +%Y%m%d)
readonly MAX_USERS=100
子进程继承行为
# parent.sh
readonly SECRET="abc123"
bash -c 'echo $SECRET; SECRET="hacked"; echo $SECRET'
# 输出:
# abc123
# hacked ← 子进程可修改!
结论 :readonly 不跨进程保护,仅在当前 Shell 防止误改。
解除只读(不可能)
readonly X=1
unset X # 失败
X=2 # 错误:readonly variable
类型 5:局部变量(Local Variable)
| 项目 |
说明 |
| 定义 |
local var=value(仅在函数内) |
| 作用域 |
当前函数(包括子函数?否!) |
| 生命周期 |
函数返回时销毁 |
| 命名惯例 |
小写,避免全局冲突 |
myfunc() {
local counter=0
local tmpfile="/tmp/data_$$"
((counter++))
echo "Counter: $counter"
}
作用域边界
global_var="outer"
func1() {
local global_var="inner"
echo "func1: $global_var" # inner
func2
}
func2() {
echo "func2: $global_var" # outer
}
func1
# 输出:
# func1: inner
# func2: outer
local 不影响子函数,仅当前函数。
错误用法
local x=1 # 在全局作用域 → 语法错误(bash 4.0+ 允许,但非标准)
类型 6:状态变量(约定俗成,非语言特性)
| 项目 |
说明 |
| 定义 |
自定义,用于流程控制 |
| 命名惯例 |
后缀 _FLAG, _STATUS, 或 0/1 布尔 |
| 常见模式 |
0=成功/假, 非0=失败/真 |
# 布尔标志
DEBUG_ENABLED=1
DRY_RUN=0
# 状态码
EXIT_SUCCESS=0
EXIT_FAILURE=1
EXIT_INVALID_ARG=2
# 字符串状态
CURRENT_STATE="initializing"
推荐判断方式
if (( DEBUG_ENABLED )); then
echo "[DEBUG] 操作: $1"
fi
if [ "$CURRENT_STATE" = "running" ]; then
:
fi
三、变量属性详解(declare 内置命令)
| 选项 |
含义 |
示例 |
-a |
数组 |
declare -a users |
-i |
整数 |
declare -i count=5 |
-l |
转小写 |
declare -l input="ABC" → abc |
-u |
转大写 |
declare -u input="abc" → ABC |
-r |
只读 |
declare -r CONST=1 |
-x |
导出(等价 export) |
declare -x VAR=1 |
-p |
显示属性 |
declare -p VAR |
+x |
取消导出 |
declare +x VAR |
declare -i number="10a" # 自动转为 10(截断非数字)
echo $number # → 10
四、变量生命周期与销毁
| 操作 |
效果 |
unset var |
删除变量 |
local var |
函数退出自动销毁 |
| Shell 退出 |
所有变量销毁 |
exec new-shell |
替换当前 Shell,所有变量丢失 |
temp=123
unset temp
echo $temp # → (空)
五、变量引用与防注入
1. 引用规则
| 场景 |
正确写法 |
错误写法 |
| 普通变量 |
"$var" |
$var(易分词错误) |
| 数组 |
"${arr[@]}" |
$arr |
| 默认值 |
${var:-default} |
$var:-default |
| 赋值默认 |
${var:=default} |
- |
| 长度 |
${#var} |
- |
| 子串 |
${var:0:3} |
- |
2. 防注入示例
filename="$1"
# 危险:用户输入 "file; rm -rf /"
cp "$filename" /backup/ # 安全
# 危险写法
cp $filename /backup/ # 分词为多个命令
六、实战脚本
#!/usr/bin/env bash
# 文件: robust-app.sh
# 描述: 演示所有变量类型的最佳实践
set -euo pipefail # 安全模式
IFS=$'\n\t' # 安全分隔符
# ==================== 环境变量 ====================
export APP_NAME="DataProcessor"
export APP_ENV="${APP_ENV:-development}" # 默认值
export LOG_DIR="/var/log/$APP_NAME"
export MAX_WORKERS=8
# ==================== 常量 ====================
readonly VERSION="3.2.1"
readonly START_TIME=$(date -u +%s)
readonly PID_FILE="/var/run/${APP_NAME}.pid"
# ==================== 状态变量 ====================
DEBUG_MODE=0
PROCESSING_STATUS="idle"
EXIT_CODE=0
# ==================== 函数 ====================
log() {
local level="$1"
local msg="$2"
local timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "[$timestamp] [$level] $msg" >&2
}
start_processing() {
local input_file="$1"
local thread_count="${2:-$MAX_WORKERS}"
# 局部变量
local start_time
local record_count=0
local temp_file
start_time=$(date +%s)
temp_file=$(mktemp "/tmp/${APP_NAME}.XXXXXX")
log "INFO" "开始处理: $input_file (线程: $thread_count)"
# 模拟处理
while IFS= read -r line; do
((record_count++))
echo "$line" >> "$temp_file"
done < "$input_file"
local duration=$(( $(date +%s) - start_time ))
log "INFO" "处理完成: $record_count 条记录,耗时 ${duration}s"
# 清理
rm -f "$temp_file"
}
# ==================== 主逻辑 ====================
main() {
(( DEBUG_MODE )) && set -x # 调试模式
log "INFO" "启动 $APP_NAME v$VERSION (PID: $$)"
if (( $# < 1 )); then
log "ERROR" "用法: $0 <输入文件> [线程数]"
EXIT_CODE=1
return
fi
local input_file="$1"
if [[ ! -f "$input_file" ]]; then
log "ERROR" "文件不存在: $input_file"
EXIT_CODE=2
return
fi
PROCESSING_STATUS="running"
start_processing "$input_file" "${2:-}"
PROCESSING_STATUS="completed"
log "INFO" "任务结束,退出码: $EXIT_CODE"
}
# 入口
main "$@"
exit $EXIT_CODE
七、附录:快速参考表
| 类型 |
定义命令 |
作用域 |
继承 |
可改 |
命名建议 |
| 普通变量 |
v=1 |
当前 Shell |
❌ |
✅ |
snake_case |
| 环境变量 |
export V=1 |
当前 + 子进程 |
✅ |
✅ |
UPPER_SNAKE |
| 位置参数 |
$1 |
当前脚本 |
❌ |
❌ |
固定 |
| 只读变量 |
readonly C=1 |
当前 Shell |
值✅ 属性❌ |
❌ |
CONSTANT |
| 局部变量 |
local x=1 |
当前函数 |
❌ |
✅ |
local_var |
| 状态变量 |
flag=1 |
视定义 |
视 export |
✅ |
is_running |