Bash 变量命名规则与类型使用

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. 首字符严格限制

bash 复制代码
valid_var=1      # ✅ 字母开头
_valid=1         # ✅ 下划线开头(常用于"私有")
1invalid=1       # ❌ 数字开头 → 语法错误

3. 大小写敏感(独立命名空间)

bash 复制代码
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, {, }, [[, ]], ((, ))
bash 复制代码
if=1             # 语法错误
local=1          # 语法错误

可通过 compgen -k 查看所有保留字。

5. 变量名长度限制

  • 理论无限制(受内存限制)
  • 实际建议:≤ 32 字符(可读性)
  • 过长会导致 bash: warning: command substitution: ignored null byte 等边缘问题

二、变量类型深度解析(定义、生命周期、继承、销毁)


类型 1:普通变量(User-Defined Variable)

项目 说明
定义 name=value(等号两边不能有空格
作用域 当前 Shell 进程
继承 不被子进程继承
生命周期 Shell 退出前存在
是否可修改
bash 复制代码
config_file="/etc/app.conf"
temp_dir="/tmp/app_$$"  # $$ 是当前 PID
常见错误
bash 复制代码
config file = "a.conf"   # 错误:被解析为命令 `config`
config_file = "a.conf"   # 错误:等号两边有空格

类型 2:环境变量(Environment Variable)

项目 说明
定义 export NAME=valueNAME=value; export NAME
作用域 当前进程 + 所有子进程
继承 通过 env 传递给子 Shell/程序
命名惯例 全大写 + 下划线(POSIX 约定)
查看 printenv, env, `set
bash 复制代码
export JAVA_HOME="/usr/lib/jvm/java-17"
export PATH="$PATH:$JAVA_HOME/bin"
继承机制详解
bash 复制代码
# parent.sh
export GREETING="Hello"
bash child.sh    # child.sh 可直接使用 $GREETING
取消导出
bash 复制代码
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 $* 关键区别
bash 复制代码
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
bash 复制代码
shift     # 丢弃 $1,$2 → $1,$3 → $2
shift 2   # 丢弃前两个参数

类型 4:只读变量(Readonly / Constant)

项目 说明
定义 readonly NAME=valuedeclare -r NAME=value
作用域 当前 Shell
继承 值被继承,但非只读(子进程可修改)
查看 readonly -p
bash 复制代码
readonly BUILD_DATE=$(date -u +%Y%m%d)
readonly MAX_USERS=100
子进程继承行为
bash 复制代码
# parent.sh
readonly SECRET="abc123"
bash -c 'echo $SECRET; SECRET="hacked"; echo $SECRET'
# 输出:
# abc123
# hacked    ← 子进程可修改!

结论readonly 不跨进程保护,仅在当前 Shell 防止误改。

解除只读(不可能)
bash 复制代码
readonly X=1
unset X    # 失败
X=2        # 错误:readonly variable

类型 5:局部变量(Local Variable)

项目 说明
定义 local var=value仅在函数内
作用域 当前函数(包括子函数?否!)
生命周期 函数返回时销毁
命名惯例 小写,避免全局冲突
bash 复制代码
myfunc() {
    local counter=0
    local tmpfile="/tmp/data_$$"
    ((counter++))
    echo "Counter: $counter"
}
作用域边界
bash 复制代码
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 不影响子函数,仅当前函数。

错误用法
bash 复制代码
local x=1    # 在全局作用域 → 语法错误(bash 4.0+ 允许,但非标准)

类型 6:状态变量(约定俗成,非语言特性)

项目 说明
定义 自定义,用于流程控制
命名惯例 后缀 _FLAG, _STATUS, 或 0/1 布尔
常见模式 0=成功/假, 非0=失败/真
bash 复制代码
# 布尔标志
DEBUG_ENABLED=1
DRY_RUN=0

# 状态码
EXIT_SUCCESS=0
EXIT_FAILURE=1
EXIT_INVALID_ARG=2

# 字符串状态
CURRENT_STATE="initializing"
推荐判断方式
bash 复制代码
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
bash 复制代码
declare -i number="10a"  # 自动转为 10(截断非数字)
echo $number  # → 10

四、变量生命周期与销毁

操作 效果
unset var 删除变量
local var 函数退出自动销毁
Shell 退出 所有变量销毁
exec new-shell 替换当前 Shell,所有变量丢失
bash 复制代码
temp=123
unset temp
echo $temp  # → (空)

五、变量引用与防注入

1. 引用规则

场景 正确写法 错误写法
普通变量 "$var" $var(易分词错误)
数组 "${arr[@]}" $arr
默认值 ${var:-default} $var:-default
赋值默认 ${var:=default} -
长度 ${#var} -
子串 ${var:0:3} -

2. 防注入示例

bash 复制代码
filename="$1"
# 危险:用户输入 "file; rm -rf /"
cp "$filename" /backup/    # 安全

# 危险写法
cp $filename /backup/      # 分词为多个命令

六、实战脚本

bash 复制代码
#!/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

相关推荐
在人间负债^2 小时前
Rust 实战项目:TODO 管理器
开发语言·后端·rust
濊繵2 小时前
Linux网络--Socket 编程 TCP
linux·网络·tcp/ip
爱吃烤鸡翅的酸菜鱼2 小时前
用【rust】实现命令行音乐播放器
开发语言·后端·rust
全栈陈序员2 小时前
用Rust和Bevy打造2D平台游戏原型
开发语言·rust·游戏引擎·游戏程序
黛琳ghz2 小时前
用 Rust 从零构建高性能文件加密工具
开发语言·后端·rust
悟世君子2 小时前
Rust 开发环境搭建
开发语言·后端·rust
代码狂想家2 小时前
Rust时序数据库实现:从压缩算法到并发优化的实战之旅
开发语言·rust·时序数据库
黛琳ghz2 小时前
用 Rust 打造高性能 PNG 压缩服务
开发语言·后端·rust
IT闫2 小时前
Rust的内存安全与实战落地的直观解析
开发语言·安全·rust