教程 linux tutorial

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 $PATH

2.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_dir

3.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
第三个参数: arg3

3.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]}"
done

5.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.sh

7. 变量的高级用法

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}"  # 输出: Alice

7.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\Documents

9. 环境变量配置文件

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
# 或者
. ~/.bashrc

10. 变量使用最佳实践

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
fi

10.3 变量调试技巧

bash
# 使用set -x启用调试模式,会显示执行的每条命令及其展开后的形式
set -x

variable="test"
echo "$variable"

# 使用set +x关闭调试模式
set +x

# 打印所有变量和函数
declare -p

# 只打印数组
declare -a

# 只打印函数
declare -f

11. 变量相关的内置命令

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_function

11.3 export 命令

bash
# 导出单个变量
export PATH

# 定义并导出变量
export DB_HOST="localhost"

# 导出多个变量
export USER_NAME="admin" PASSWORD="secret"

# 查看所有导出的变量
export -p

12. 变量实例与应用

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]}"
done

12.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脚本中灵活应用这些知识来编写更加高效和健壮的脚本程序。