主题
Shell 变量
1. Shell 变量基础
1.1 什么是Shell变量
Shell变量是Shell中存储数据的一种方式,可以将字符串、数字或命令输出等赋值给变量,然后在脚本或命令行中引用这些变量。Shell变量是大小写敏感的,通常使用大写字母表示环境变量,使用小写字母表示局部变量。
1.2 变量命名规则
Shell变量的命名需要遵循以下规则:
- 变量名只能包含字母、数字和下划线
- 变量名必须以字母或下划线开头,不能以数字开头
- 变量名不能包含空格或特殊字符
- Shell变量区分大小写
以下是有效的变量名示例:
bash
name
user_name
USER_NAME
_count
SHELL2以下是无效的变量名示例:
bash
1name # 不能以数字开头
user-name # 不能包含连字符
user name # 不能包含空格
user@name # 不能包含特殊字符1.3 变量的定义和赋值
在Shell中,可以通过简单的赋值语句来定义变量:
bash
变量名=值注意,变量名和等号之间不能有空格,等号和值之间也不能有空格。
示例:
bash
# 定义字符串变量
name="John Doe"
age=30
# 定义命令输出作为变量值
date_today=$(date +"%Y-%m-%d")
# 使用反引号(较旧的语法)
host_name=`hostname`2. 变量的使用
2.1 引用变量
要在Shell中引用变量的值,可以使用$变量名或${变量名}语法。使用花括号{}可以在变量名后直接添加其他字符,避免歧义。
示例:
bash
name="John"
echo "Hello, $name!"
echo "Hello, ${name}Doe!" # 输出: Hello, JohnDoe!2.2 修改变量值
可以通过重新赋值来修改已定义变量的值:
bash
count=10
echo "当前计数: $count"
count=20
echo "更新后的计数: $count"2.3 显示所有变量
可以使用以下命令显示所有已定义的变量:
bash
# 显示所有环境变量和局部变量
env
printenv
# 显示所有变量(包括局部变量和函数)
set
# 使用grep过滤特定变量
env | grep PATH
echo $PATH2.4 删除变量
使用unset命令可以删除已定义的变量:
bash
name="John"
echo "$name" # 输出: John
unset name
echo "$name" # 输出空行,变量已被删除3. 变量类型
3.1 局部变量
局部变量是在当前Shell进程中定义的变量,只在定义它的Shell或Shell脚本中可见,对其他Shell进程不可见。
bash
# 在当前Shell中定义局部变量
local_var="这是一个局部变量"
echo $local_var # 输出变量值在Shell函数中,可以使用local关键字定义只在函数内部可见的变量:
bash
my_function() {
local local_var="函数内部变量"
echo "函数内: $local_var"
}
my_function # 输出: 函数内: 函数内部变量
echo "函数外: $local_var" # 输出空行或其他值3.2 环境变量
环境变量是对当前Shell及其所有子Shell可见的变量。环境变量通常用于存储系统配置信息,如PATH、HOME等。
使用export命令可以将局部变量转换为环境变量:
bash
# 定义并导出环境变量
export PATH="$PATH:/usr/local/bin"
export LANG="zh_CN.UTF-8"
# 另一种方式:先定义变量,再导出
home_dir="/home/user"
export home_dir3.3 位置参数
位置参数是Shell脚本接收的命令行参数,使用$1, $2, $3等表示第一个、第二个、第三个参数,依此类推。$0表示脚本本身的名称。
示例脚本parameters.sh:
bash
#!/bin/bash
echo "脚本名称: $0"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "第三个参数: $3"运行方式:
bash
chmod +x parameters.sh
./parameters.sh arg1 arg2 arg3输出结果:
脚本名称: ./parameters.sh
第一个参数: arg1
第二个参数: arg2
第三个参数: arg33.4 特殊变量
Shell提供了一系列特殊变量,用于获取命令行参数数量、退出状态等信息:
| 变量 | 描述 |
|---|---|
$0 | 脚本名称 |
$1, $2, ... | 位置参数 |
$# | 命令行参数的个数 |
$* | 所有位置参数,作为一个单词 |
$@ | 所有位置参数,每个参数作为独立的单词 |
$? | 上一个命令的退出状态(0表示成功,非0表示失败) |
$$ | 当前进程的PID |
$! | 上一个后台进程的PID |
$_ | 上一个命令的最后一个参数 |
$HOME | 用户主目录 |
$PATH | 命令搜索路径 |
$LANG | 当前语言环境 |
$USER | 当前用户名 |
$HOSTNAME | 主机名 |
示例脚本special_vars.sh:
bash
#!/bin/bash
echo "脚本名称: $0"
echo "参数数量: $#"
echo "所有参数(\$*): $*"
echo "所有参数(\$@): $@"
echo "当前进程PID: $$"
# 执行一个命令并查看退出状态
echo "测试命令"
command_status=$?
echo "命令退出状态: $command_status"4. 变量展开和替换
4.1 参数展开
Shell提供了强大的参数展开功能,可以在引用变量时进行各种操作:
bash
# 基本展开
name="John"
echo "Hello, $name!"
# 默认值:如果变量未设置或为空,使用默认值
user_name=${name:-Guest}
echo "用户名: $user_name"
# 强制赋值:如果变量未设置或为空,设置并使用默认值
user_age=${age:=18}
echo "年龄: $user_age"
# 错误处理:如果变量未设置或为空,显示错误信息并退出
# ${required_var:?变量未设置}
# 替代值:如果变量已设置且非空,使用替代值,否则为空
capital_name=${name:+JOHN}
echo "大写名称: $capital_name"
# 字符串长度
echo "名称长度: ${#name}"4.2 字符串操作
可以对变量进行各种字符串操作,如截取、替换等:
bash
path="/home/user/documents/report.txt"
# 提取子字符串(从索引0开始,长度为5)
echo "前5个字符: ${path:0:5}"
# 从指定索引到末尾
echo "从第6个字符开始: ${path:6}"
# 从字符串开头删除匹配的最短子串
echo "删除/home/: ${path#/home/}"
# 从字符串开头删除匹配的最长子串
echo "删除直到最后一个/: ${path##*/}"
# 从字符串结尾删除匹配的最短子串
echo "删除.txt: ${path%.txt}"
# 从字符串结尾删除匹配的最长子串
echo "删除扩展名: ${path%.*}"
# 替换第一个匹配的子串
echo "替换user为admin: ${path/user/admin}"
# 替换所有匹配的子串
echo "替换所有o为O: ${path//o/O}"5. 数组变量
5.1 数组定义和访问
在Shell中,可以定义数组变量来存储多个值:
bash
# 定义数组
fruits=('Apple' 'Banana' 'Orange' 'Grape')
# 另一种定义方式
colors[0]="Red"
colors[1]="Green"
colors[2]="Blue"
# 访问数组元素
echo "第一个水果: ${fruits[0]}"
echo "第三个颜色: ${colors[2]}"
# 访问所有数组元素
echo "所有水果: ${fruits[@]}"
echo "所有颜色: ${colors[*]}"
# 获取数组长度
echo "水果数量: ${#fruits[@]}"5.2 数组操作
bash
# 添加元素到数组末尾
fruits+=('Mango')
# 更新数组元素
fruits[1]='Pineapple'
# 删除数组元素
unset fruits[2]
# 遍历数组
echo "遍历水果数组:"
for fruit in "${fruits[@]}"; do
echo "- $fruit"
done
# 使用索引遍历数组
echo "使用索引遍历颜色数组:"
for ((i=0; i<${#colors[@]}; i++)); do
echo "索引 $i: ${colors[i]}"
done5.3 数组切片
bash
# 数组切片:获取子数组
numbers=(1 2 3 4 5 6 7 8 9 10)
# 从索引2开始,取3个元素
echo "切片 [2:5]: ${numbers[@]:2:3}"
# 从索引3到末尾
echo "切片 [3:]: ${numbers[@]:3}"6. 变量作用域
6.1 全局变量
在脚本的顶层定义的变量默认为全局变量,可以在脚本的任何地方访问,包括函数内部:
bash
global_var="这是全局变量"
display_var() {
echo "函数内访问全局变量: $global_var"
# 在函数内修改全局变量
global_var="修改后的全局变量"
}
echo "函数调用前: $global_var"
display_var
echo "函数调用后: $global_var"6.2 局部变量
在函数内部使用local关键字定义的变量是局部变量,只在函数内部可见:
bash
outer_var="外部变量"
demo_function() {
local inner_var="内部变量"
echo "函数内访问局部变量: $inner_var"
echo "函数内访问外部变量: $outer_var"
# 修改外部变量
outer_var="函数内修改的外部变量"
}
echo "函数调用前: $outer_var"
demo_function
echo "函数调用后: $outer_var"
# 以下行将报错,因为inner_var在函数外部不可见
# echo "函数外部访问局部变量: $inner_var"6.3 环境变量作用域
环境变量在当前Shell及其所有子Shell中可见:
bash
# 创建一个脚本示例 (child_script.sh)
cat > child_script.sh << 'EOF'
#!/bin/bash
echo "子脚本中访问环境变量: $MY_ENV_VAR"
echo "子脚本中访问局部变量: $MY_LOCAL_VAR"
EOF
chmod +x child_script.sh
# 设置局部变量和环境变量
MY_LOCAL_VAR="这是局部变量"
export MY_ENV_VAR="这是环境变量"
# 在当前Shell中访问
echo "当前Shell中访问局部变量: $MY_LOCAL_VAR"
echo "当前Shell中访问环境变量: $MY_ENV_VAR"
# 在子脚本中访问
./child_script.sh7. 变量的高级用法
7.1 命令替换
可以将命令的输出赋值给变量:
bash
# 使用 $(command) 语法(推荐)
date_today=$(date +"%Y-%m-%d")
file_count=$(ls -l | wc -l)
# 使用反引号语法(较旧的方式)
current_dir=`pwd`
# 在字符串中嵌入命令替换
message="今天是 $(date +"%Y-%m-%d"),共有 $(ls -l | wc -l) 个文件。"
echo "$message"7.2 间接变量引用
间接变量引用允许通过另一个变量的值作为变量名来访问变量:
bash
# 使用 ${!var} 语法
first_name="John"
last_name="Doe"
full_name="first_name last_name"
# 直接引用
name_var="first_name"
echo "直接引用: ${!name_var}" # 输出: John
# 组合变量名
prefix="user"
suffix="name"
user_name="Alice"
var_name="${prefix}${suffix}"
echo "组合变量名: ${!var_name}" # 输出: Alice7.3 变量类型转换
Shell变量本质上都是字符串,但可以进行算术运算:
bash
# 算术运算
a=10
b=20
# 使用 $((expression)) 语法
c=$((a + b))
d=$((a * b))
e=$((b / a))
# 也可以使用 let 命令
let "f = a + 5"
# 浮点数运算需要使用 bc 命令
x=10.5
y=5.2
result=$(echo "$x + $y" | bc)
# 转换为字符串(Shell变量默认就是字符串)
number=42
string="$number"
# 字符串拼接
greeting="Hello, "
name="World"
message="$greeting$name!"8. 变量和引号
8.1 不同引号的作用
在Shell中,引号对变量的展开有不同的影响:
bash
name="John Doe"
# 双引号:变量会被展开
echo "双引号: Hello, $name!"
# 输出: 双引号: Hello, John Doe!
# 单引号:变量不会被展开
echo '单引号: Hello, $name!'
# 输出: 单引号: Hello, $name!
# 反引号:执行命令并替换结果
echo "反引号: 今天是 `date +"%Y-%m-%d"`"
# 输出: 反引号: 今天是 2023-07-15(取决于当前日期)8.2 转义字符
使用反斜杠\可以转义特殊字符,使其被当作普通字符处理:
bash
# 转义美元符号,使其不被当作变量
price=100
echo "价格是 \$${price}"
# 输出: 价格是 $100
# 转义引号
echo "他说\"Hello, World!\""
# 输出: 他说"Hello, World!"
# 转义反斜杠
echo "路径是 C:\\Users\\Documents"
# 输出: 路径是 C:\Users\Documents9. 环境变量配置文件
9.1 常用配置文件
Linux系统中有多个配置文件用于设置环境变量,这些文件在不同的登录情况下会被加载:
| 配置文件 | 描述 | 加载时机 |
|---|---|---|
/etc/profile | 系统级别的全局配置 | 所有用户登录时加载 |
/etc/bash.bashrc | 系统级别的Bash配置 | 每个新的Bash shell启动时加载 |
~/.profile | 用户级别的配置 | 用户登录时加载 |
~/.bash_profile | 用户级别的Bash配置 | Bash登录时加载 |
~/.bashrc | 用户级别的Bash配置 | 每个新的Bash shell启动时加载 |
~/.bash_login | 用户级别的登录配置 | 用户登录时加载(如果没有~/.bash_profile) |
9.2 永久设置环境变量
要永久设置环境变量,可以将变量定义添加到适当的配置文件中:
bash
# 编辑个人Bash配置文件
nano ~/.bashrc
# 添加环境变量定义
export PATH="$PATH:/usr/local/bin"
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk"
export EDITOR="vim"
# 保存退出后,重新加载配置文件
source ~/.bashrc
# 或者
. ~/.bashrc10. 变量使用最佳实践
10.1 命名约定
- 环境变量:使用全大写字母,单词之间用下划线分隔(如
JAVA_HOME) - 局部变量:使用小写字母,单词之间用下划线分隔(如
user_name) - 常量:使用全大写字母(如
MAX_SIZE=100) - 临时变量:可以使用简短的名称(如
i,j等循环变量)
10.2 安全使用变量
- 始终使用双引号包围变量引用,避免因变量值中的空格或特殊字符导致的问题
- 使用
${变量名}而不是$变量名,特别是在变量名后直接跟其他字符时 - 验证输入变量,避免注入攻击
bash
# 不安全的做法
file=$1
cat $file # 如果file包含恶意输入如 "/etc/passwd; rm -rf /" 会非常危险
# 安全的做法
file="$1"
# 验证文件是否存在且为普通文件
if [ -f "$file" ]; then
cat "$file"
else
echo "错误: 文件不存在或不是普通文件"
exit 1
fi10.3 变量调试技巧
bash
# 使用set -x启用调试模式,会显示执行的每条命令及其展开后的形式
set -x
variable="test"
echo "$variable"
# 使用set +x关闭调试模式
set +x
# 打印所有变量和函数
declare -p
# 只打印数组
declare -a
# 只打印函数
declare -f11. 变量相关的内置命令
11.1 declare/typeset 命令
declare命令用于声明变量类型和属性:
bash
# 声明只读变量
declare -r readonly_var="这是只读变量"
# 或者使用 readonly 命令
readonly readonly_var2="另一个只读变量"
# 声明整数变量
declare -i int_var=10
int_var=$int_var+5 # 自动进行算术运算,结果为15
# 声明关联数组(类似字典)
declare -A colors
colors["red"]="#FF0000"
colors["green"]="#00FF00"
colors["blue"]="#0000FF"
echo "红色: ${colors[red]}"
# 声明局部变量(在函数中使用)
declare -l lower_var="HELLO" # 自动转换为小写11.2 unset 命令
bash
# 删除单个变量
my_var="test"
unset my_var
# 删除数组的单个元素
my_array=(1 2 3 4 5)
unset my_array[2]
# 删除整个数组
unset my_array
# 删除函数
my_function() {
echo "Hello"
}
unset -f my_function11.3 export 命令
bash
# 导出单个变量
export PATH
# 定义并导出变量
export DB_HOST="localhost"
# 导出多个变量
export USER_NAME="admin" PASSWORD="secret"
# 查看所有导出的变量
export -p12. 变量实例与应用
12.1 基本变量应用示例
bash
#!/bin/bash
# 定义变量
SCRIPT_NAME="系统信息收集器"
SCRIPT_VERSION="1.0"
HOST_NAME=$(hostname)
CURRENT_DATE=$(date +"%Y-%m-%d %H:%M:%S")
# 显示欢迎信息
echo "========================================"
echo "$SCRIPT_NAME v$SCRIPT_VERSION"
echo "主机: $HOST_NAME"
echo "日期: $CURRENT_DATE"
echo "========================================"
# 收集系统信息
CPU_INFO=$(cat /proc/cpuinfo | grep 'model name' | head -1 | awk -F: '{print $2}' | sed 's/^[ \t]*//')
MEMORY_INFO=$(free -h | grep Mem | awk '{print $2}')
DISK_INFO=$(df -h / | grep / | awk '{print $2, $5}')
# 显示系统信息
echo "\n系统信息:"
echo "CPU: $CPU_INFO"
echo "内存: $MEMORY_INFO"
echo "磁盘: $DISK_INFO"
echo "\n脚本执行完成!"12.2 数组变量应用示例
bash
#!/bin/bash
# 定义应用列表
APPLICATIONS=("Apache" "MySQL" "PHP" "Node.js" "Docker")
VERSIONS=("2.4.51" "8.0.26" "7.4.24" "16.11.0" "20.10.8")
# 显示应用列表
echo "已安装应用列表:"
echo "=================="
# 遍历数组显示应用及其版本
for ((i=0; i<${#APPLICATIONS[@]}; i++)); do
echo "${APPLICATIONS[$i]}: ${VERSIONS[$i]}"
done
# 添加新应用
APPLICATIONS+=("Redis")
VERSIONS+=("6.2.6")
echo "\n更新后的应用列表:"
echo "=================="
for ((i=0; i<${#APPLICATIONS[@]}; i++)); do
echo "${APPLICATIONS[$i]}: ${VERSIONS[$i]}"
done12.3 环境变量应用示例
bash
#!/bin/bash
# 检查并设置必要的环境变量
if [ -z "$JAVA_HOME" ]; then
echo "警告: JAVA_HOME 环境变量未设置"
# 尝试自动检测
if [ -d "/usr/lib/jvm/java-11-openjdk" ]; then
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk"
echo "已自动设置 JAVA_HOME=$JAVA_HOME"
fi
fi
# 将Java bin目录添加到PATH(如果尚未添加)
if [[ "$PATH" != *"$JAVA_HOME/bin"* ]]; then
export PATH="$JAVA_HOME/bin:$PATH"
echo "已将Java添加到PATH"
fi
# 创建应用配置目录
APP_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/myapp"
mkdir -p "$APP_CONFIG_DIR"
echo "配置目录: $APP_CONFIG_DIR"
echo "Java版本: $(java -version 2>&1 | head -1)"通过本章的学习,您应该掌握了Shell变量的定义、使用、作用域和高级特性,可以在Shell脚本中灵活应用这些知识来编写更加高效和健壮的脚本程序。