Git 基本操作

Shell变量详解

掌握Shell脚本中的变量使用技巧

从基础定义到高级应用

什么是Shell变量?

Shell变量是用于存储数据的命名实体,是Shell脚本编程的基础组成部分。通过使用变量,我们可以:

存储数据

保存字符串、数字、文件名等各类数据,供脚本后续使用。

提高重用性

通过修改变量值,可以轻松改变脚本行为,无需修改代码。

简化维护

将常用值定义为变量,便于统一管理和修改。

增强交互

通过变量接收用户输入,使脚本更加灵活和交互式。

变量命名规则
  • 变量名只能包含字母、数字和下划线
  • 变量名不能以数字开头
  • 变量名区分大小写(NAMEname 是不同的变量)
  • 避免使用Shell关键字作为变量名
  • 建议使用大写字母表示环境变量,小写字母表示局部变量

Shell变量类型

Shell中有多种类型的变量,每种类型有不同的作用域和生命周期。

局部变量

在当前Shell会话中定义的变量,只在当前脚本或Shell会话中可见。

Terminal
# 定义局部变量
name="Alice"
age=25

# 使用变量
echo "Name: $name, Age: $age"

# 变量只在当前Shell中有效
# 在子Shell或新终端中不可见

环境变量

在整个Shell会话及其子进程中可见的变量,通常用于系统配置。

Terminal
# 定义环境变量
export DATABASE_URL="postgres://user:pass@localhost/db"
export PATH="$PATH:/usr/local/bin"

# 查看环境变量
echo $PATH
env | grep DATABASE

# 环境变量对子进程可见

特殊变量

由Shell自动设置的特殊变量,提供脚本执行的相关信息。

Terminal
# 特殊变量示例
echo "脚本名称: $0"
echo "第一个参数: $1"
echo "所有参数: $@"
echo "参数个数: $#"
echo "进程ID: $$"
echo "退出状态: $?"

数组变量

可以存储多个值的变量,通过索引访问各个元素。

Terminal
# 定义数组
fruits=("apple" "banana" "orange")

# 访问数组元素
echo "第一个水果: ${fruits[0]}"
echo "所有水果: ${fruits[@]}"
echo "水果数量: ${#fruits[@]}"

# 添加元素
fruits+=("grape")
变量作用域总结
变量类型 作用域 定义方式 示例
局部变量 当前Shell var=value name="Alice"
环境变量 当前Shell及子进程 export var=value export PATH="/usr/bin"
只读变量 当前Shell readonly var=value readonly PI=3.14
位置参数 当前脚本 自动设置 $1, $2, $@

变量使用方法

学习如何定义、使用和操作Shell变量。

变量定义与赋值

在Shell中定义变量并为其赋值。

variable_assignment.sh
#!/bin/bash

# 基本变量赋值(等号两边不能有空格)
name="Alice"
age=25
price=19.99

# 使用命令输出赋值
current_date=$(date)
file_count=$(ls | wc -l)

# 使用引号处理包含空格的字符串
message="Hello, World!"
file_path="/home/user/documents/file name.txt"

# 只读变量(不可修改)
readonly PI=3.14159

# 删除变量
unset name
赋值注意事项:
  • 等号两边不能有空格name="Alice"(正确) vs name = "Alice"(错误)
  • 变量值包含空格时需要使用引号
  • 使用$(command)或反引号`command`获取命令输出

变量引用与扩展

使用变量值并进行各种扩展操作。

variable_reference.sh
#!/bin/bash

name="Alice"
path="/home/user/file.txt"

# 基本引用
echo "Hello, $name"
echo "Path: $path"

# 使用大括号明确变量边界
echo "Hello, ${name}"
echo "File: ${path}.bak"

# 当变量名后紧跟其他字符时必须使用大括号
echo "Hello, ${name}smith"  # 正确
echo "Hello, $namesmith"    # 错误(会尝试访问namesmith变量)

# 变量默认值
echo "Username: ${USER:-guest}"
echo "Home: ${HOME:-/tmp}"

# 检查变量是否设置
echo "Name is set: ${name+yes}"
echo "Undefined is set: ${undefined+no}"

字符串操作

对字符串变量进行各种操作和处理。

string_operations.sh
#!/bin/bash

text="Hello, World! This is a test string."

# 字符串长度
echo "Length: ${#text}"

# 提取子字符串
echo "Substring(0,5): ${text:0:5}"     # Hello
echo "Substring(7): ${text:7}"         # World! This is a test string.

# 字符串替换
echo "Replace World: ${text/World/Shell}"        # 替换第一个匹配
echo "Replace all s: ${text//s/S}"               # 替换所有匹配
echo "Replace prefix: ${text/#Hello/Hi}"         # 替换开头
echo "Replace suffix: ${text/%./!}"              # 替换结尾

# 大小写转换
name="alice"
echo "Uppercase: ${name^^}"            # ALICE
echo "Lowercase: ${name,,}"            # alice (已经是小写)

# 删除匹配模式
filename="backup.tar.gz"
echo "Remove .tar.gz: ${filename%.tar.gz}"    # backup
echo "Remove backup.: ${filename#backup.}"    # tar.gz

特殊变量

Shell提供了一系列自动设置的特殊变量,用于获取脚本执行的相关信息。

变量 描述 示例
$0 当前脚本的名称 script.sh
$1, $2, ... $9 脚本的参数(第1-9个) $1 是第一个参数
$# 传递给脚本的参数个数 如果传递3个参数,$# 为3
$@ 所有参数(作为单独的单词) "$1" "$2" "$3" ...
$* 所有参数(作为单个单词) "$1 $2 $3 ..."
$? 最后命令的退出状态 0表示成功,非0表示错误
$$ 当前Shell的进程ID 脚本运行的进程ID
$! 最后执行的后台进程的PID 后台进程的进程ID

特殊变量使用示例

special_variables.sh
#!/bin/bash

# 特殊变量示例脚本
echo "脚本名称: $0"
echo "参数个数: $#"
echo "所有参数: $@"

# 处理参数
if [ $# -gt 0 ]; then
    echo "第一个参数: $1"
    echo "第二个参数: $2"
fi

# 使用shift处理多个参数
echo "--- 使用shift处理参数 ---"
count=1
while [ $# -gt 0 ]; do
    echo "参数 $count: $1"
    shift
    count=$((count + 1))
done

# 检查命令执行状态
ls /nonexistent > /dev/null 2>&1
echo "ls命令退出状态: $?"

# 显示进程信息
echo "当前进程ID: $$"
echo "父进程ID: $PPID"

# 后台进程示例
sleep 10 &
echo "后台进程ID: $!"
$@$* 的区别:

在引号内使用时,"$@" 将每个参数作为独立的单词处理,而 "$*" 将所有参数作为一个单词处理。在循环处理参数时,通常使用 "$@"

数组变量

Shell支持一维数组,可以存储多个值并通过索引访问。

数组基本操作

array_basics.sh
#!/bin/bash

# 定义数组的不同方式
fruits=("apple" "banana" "orange")
colors=(red green blue)
numbers=({1..5})  # 1 2 3 4 5

# 逐个元素赋值
days[0]="Monday"
days[1]="Tuesday"
days[2]="Wednesday"

# 访问数组元素
echo "第一个水果: ${fruits[0]}"
echo "第二个颜色: ${colors[1]}"
echo "所有数字: ${numbers[@]}"

# 数组长度
echo "水果数量: ${#fruits[@]}"
echo "颜色数量: ${#colors[@]}"

# 遍历数组
echo "--- 所有水果 ---"
for fruit in "${fruits[@]}"; do
    echo "水果: $fruit"
done

echo "--- 带索引遍历 ---"
for i in "${!fruits[@]}"; do
    echo "索引 $i: ${fruits[$i]}"
done

数组高级操作

array_advanced.sh
#!/bin/bash

# 初始化数组
items=("one" "two" "three" "four" "five")

# 添加元素
items+=("six")
items=("${items[@]}" "seven")

# 删除元素
unset items[2]  # 删除第三个元素("three")
echo "删除后: ${items[@]}"

# 重新索引数组(删除元素后索引可能不连续)
items=("${items[@]}")
echo "重新索引后: ${items[@]}"

# 数组切片
echo "前三个元素: ${items[@]:0:3}"
echo "从第二个开始: ${items[@]:2}"

# 数组合并
array1=("A" "B" "C")
array2=("D" "E" "F")
combined=("${array1[@]}" "${array2[@]}")
echo "合并数组: ${combined[@]}"

# 将命令输出转换为数组
files=($(ls *.txt 2>/dev/null))
echo "文本文件: ${files[@]}"

# 关联数组(Bash 4.0+)
declare -A user
user[name]="Alice"
user[age]=25
user[city]="Beijing"
echo "用户名: ${user[name]}"
echo "用户年龄: ${user[age]}"

综合示例

通过实际脚本示例展示Shell变量的综合应用。

系统信息收集脚本

使用各种变量类型收集和显示系统信息。

system_info.sh
#!/bin/bash

# 系统信息收集脚本

# 定义颜色变量(用于输出美化)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 系统信息变量
hostname=$(hostname)
os_name=$(grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '"')
kernel_version=$(uname -r)
architecture=$(uname -m)

# 用户信息变量
current_user=$(whoami)
user_home=$HOME
shell_type=$SHELL

# 系统资源变量
memory_info=$(free -h | grep Mem | awk '{print $2}')
disk_usage=$(df -h / | awk 'NR==2 {print $5}')
load_average=$(uptime | awk -F'load average:' '{print $2}')

# 当前时间变量
current_time=$(date '+%Y-%m-%d %H:%M:%S')
uptime_info=$(uptime -p)

# 输出系统信息
echo -e "${BLUE}=== 系统信息 ===${NC}"
echo -e "主机名: ${GREEN}$hostname${NC}"
echo -e "操作系统: ${GREEN}$os_name${NC}"
echo -e "内核版本: ${GREEN}$kernel_version${NC}"
echo -e "系统架构: ${GREEN}$architecture${NC}"

echo -e "\n${BLUE}=== 用户信息 ===${NC}"
echo -e "当前用户: ${GREEN}$current_user${NC}"
echo -e "用户家目录: ${GREEN}$user_home${NC}"
echo -e "Shell类型: ${GREEN}$shell_type${NC}"

echo -e "\n${BLUE}=== 资源使用 ===${NC}"
echo -e "内存总量: ${GREEN}$memory_info${NC}"
echo -e "根分区使用率: ${GREEN}$disk_usage${NC}"
echo -e "系统负载: ${GREEN}$load_average${NC}"

echo -e "\n${BLUE}=== 时间信息 ===${NC}"
echo -e "当前时间: ${GREEN}$current_time${NC}"
echo -e "运行时间: ${GREEN}$uptime_info${NC}"

# 使用数组存储网络接口信息
interfaces=($(ip link show | grep -E '^[0-9]+:' | cut -d: -f2 | tr -d ' '))

echo -e "\n${BLUE}=== 网络接口 ===${NC}"
for interface in "${interfaces[@]}"; do
    # 跳过lo接口
    if [[ "$interface" != "lo" ]]; then
        ip_addr=$(ip addr show "$interface" | grep -E 'inet ' | awk '{print $2}' | head -1)
        if [ -n "$ip_addr" ]; then
            echo -e "$interface: ${GREEN}$ip_addr${NC}"
        else
            echo -e "$interface: ${YELLOW}未分配IP${NC}"
        fi
    fi
done

# 脚本结束信息
echo -e "\n${GREEN}系统信息收集完成!${NC}"

变量使用最佳实践

推荐做法
  • 使用有意义的变量名
  • 局部变量使用小写,环境变量使用大写
  • 对包含空格的字符串使用引号
  • 使用${var}而不是$var提高可读性
  • 在脚本开头定义所有变量
  • 使用只读变量保护常量
避免的做法
  • 避免使用特殊字符命名变量
  • 不要使用Shell关键字作为变量名
  • 避免在赋值时等号两边加空格
  • 不要依赖未初始化的变量
  • 避免过度使用全局变量
  • 不要忘记处理变量中的特殊字符
调试技巧

使用 set -x 开启调试模式,可以查看变量的实际展开过程。使用 echoprintf 输出变量值进行调试。