变量与数据类型
变量是 Shell 脚本的基础构建块。本章详细介绍 Shell 变量的定义、使用、作用域,以及数组、特殊变量和环境变量等核心概念。
变量定义与赋值
Shell 变量是弱类型的,不需要声明类型,直接赋值即可创建变量。
基本语法
variable_name=value
重要规则:等号两边不能有空格!
# 正确的赋值
name="John"
age=25
active=true
# 错误的赋值(等号两边有空格)
name = "John" # 语法错误!会被解释为执行 name 命令,参数为 "=" 和 "John"
为什么不能有空格
在 Shell 中,空格是词分隔符。name="John" 是一个赋值语句,而 name = "John" 会被解析为执行名为 name 的命令,带有两个参数 = 和 "John"。
变量命名规则
- 只能包含字母、数字和下划线
- 必须以字母或下划线开头
- 区分大小写
- 不能使用 Shell 保留字(如 if, then, else 等)
# 合法的变量名
name="John"
_name="John"
NAME="John"
var1="John"
my_var="John"
# 不合法的变量名
1var="John" # 不能以数字开头
my-var="John" # 不能包含连字符
变量使用
使用变量时,在变量名前加 $ 符号:
name="John"
echo $name # 输出:John
echo ${name} # 输出:John(推荐使用花括号)
花括号 {} 用于明确变量名的边界,在变量名后面紧跟其他字符时必须使用:
name="John"
echo "Hello, ${name}!" # 输出:Hello, John!
echo "Hello, $name!" # 也输出:Hello, John!
# 必须使用花括号的情况
echo "${name}s" # 输出:Johns
echo "$names" # 输出为空,因为 $names 是另一个变量
只读变量
使用 readonly 命令可以将变量设为只读,之后不能修改:
name="John"
readonly name
name="Mike" # 报错:name: readonly variable
也可以在定义时直接声明为只读:
readonly PI=3.14159
删除变量
使用 unset 命令删除变量:
name="John"
unset name
echo $name # 输出为空
注意:不能删除只读变量。
数据类型
Shell 变量本质上是字符串类型,但根据上下文可以解释为其他类型。
字符串
字符串是 Shell 中最基本的数据类型,可以使用单引号、双引号或不加引号。
# 不加引号(不能包含空格和特殊字符)
name=John
# 单引号(保留字面值)
name='John Doe'
# 双引号(支持变量扩展和转义)
greeting="Hello, $name"
整数
Shell 变量可以存储整数值,使用 declare -i 声明整数变量:
declare -i count
count=10
count="hello" # 字符串会被当作 0
echo $count # 输出:0
# 整数变量可以直接进行算术运算
declare -i x=10
x=x+5
echo $x # 输出:15
数组
Bash 支持一维数组(索引数组和关联数组)。
布尔值
Shell 没有真正的布尔类型,通常使用整数 0 表示真,非 0 表示假,或者使用字符串 "true" 和 "false"。
# 在条件判断中
if true; then
echo "This is true"
fi
# 自定义布尔变量
is_valid=0 # 0 表示真
if [ $is_valid -eq 0 ]; then
echo "Valid"
fi
字符串操作
Shell 提供了丰富的字符串操作功能。
获取字符串长度
str="Hello, World!"
echo ${#str} # 输出:13
# 或者使用 expr
expr length "$str" # 输出:13
字符串拼接
Shell 中字符串拼接非常简单,直接相邻即可:
first="Hello"
last="World"
full="$first, $last!"
echo $full # 输出:Hello, World!
# 直接拼接
str="Hello"
str="${str} World"
echo $str # 输出:Hello World
子字符串
使用 ${string:position:length} 提取子字符串:
str="Hello, World!"
# 从位置 0 开始提取 5 个字符
echo ${str:0:5} # 输出:Hello
# 从位置 7 开始提取到末尾
echo ${str:7} # 输出:World!
# 从右边开始计数(负数索引)
echo ${str: -6} # 输出:World!(注意空格)
echo ${str:0-6:5} # 输出:World
字符串查找
str="Hello, World!"
# 查找子字符串位置(从 1 开始计数,0 表示未找到)
expr index "$str" "W" # 输出:8
# 查找子字符串
case "$str" in
*World*) echo "Found" ;;
*) echo "Not found" ;;
esac
# 使用 grep
if echo "$str" | grep -q "World"; then
echo "Found"
fi
字符串替换
使用参数扩展进行字符串替换:
str="Hello, World! World!"
# 替换第一个匹配
echo ${str/World/Shell} # 输出:Hello, Shell! World!
# 替换所有匹配
echo ${str//World/Shell} # 输出:Hello, Shell! Shell!
# 替换开头匹配
echo ${str/#Hello/Hi} # 输出:Hi, World! World!
# 替换结尾匹配
echo ${str/%World!/Bash} # 输出:Hello, World! Bash
删除子字符串
str="Hello, World!"
# 删除最短匹配的前缀
echo ${str#Hello, } # 输出:World!
# 删除最长匹配的前缀
path="/usr/local/bin/script.sh"
echo ${path#*/} # 输出:usr/local/bin/script.sh
echo ${path##*/} # 输出:script.sh
# 删除最短匹配的后缀
echo ${str%World!} # 输出:Hello,
# 删除最长匹配的后缀
echo ${path%/*} # 输出:/usr/local/bin
echo ${path%%/*} # 输出:(空)
大小写转换
str="Hello, World!"
# 转换为小写
echo ${str,,} # 输出:hello, world!
# 转换为大写
echo ${str^^} # 输出:HELLO, WORLD!
# 首字母大写
echo ${str^} # 输出:Hello, world!
# 首字母小写
str2="HELLO"
echo ${str2,} # 输出:hELLO
数组
Bash 支持两种类型的数组:索引数组(以数字为索引)和关联数组(以字符串为索引)。
索引数组
创建数组
# 方式一:使用括号
arr=(one two three four)
# 方式二:逐个赋值
arr[0]=one
arr[1]=two
arr[2]=three
# 方式三:使用 declare
declare -a arr
arr=(one two three)
# 方式四:从命令输出创建
arr=($(ls))
访问数组元素
arr=(one two three four)
# 访问单个元素(索引从 0 开始)
echo ${arr[0]} # 输出:one
echo ${arr[2]} # 输出:three
# 访问所有元素
echo ${arr[@]} # 输出:one two three four
echo ${arr[*]} # 输出:one two three four
# 访问连续元素
echo ${arr[@]:1:2} # 输出:two three(从索引 1 开始,取 2 个)
获取数组信息
arr=(one two three four)
# 数组长度
echo ${#arr[@]} # 输出:4
echo ${#arr[*]} # 输出:4
# 单个元素长度
echo ${#arr[0]} # 输出:3
# 获取所有索引
echo ${!arr[@]} # 输出:0 1 2 3
修改数组
arr=(one two three)
# 修改元素
arr[1]=TWO
# 添加元素
arr[3]=four
arr+=(five six) # 追加多个元素
# 删除元素
unset arr[1] # 删除索引 1 的元素
unset arr # 删除整个数组
遍历数组
arr=(one two three four)
# 方式一:遍历元素
for item in "${arr[@]}"; do
echo "Item: $item"
done
# 方式二:遍历索引
for i in "${!arr[@]}"; do
echo "Index $i: ${arr[$i]}"
done
# 方式三:C 风格遍历
for ((i=0; i<${#arr[@]}; i++)); do
echo "Index $i: ${arr[$i]}"
done
关联数组
关联数组使用字符串作为索引,类似于其他语言中的字典或哈希表。
创建关联数组
# 必须使用 declare -A 声明
declare -A person
# 赋值
person[name]="John"
person[age]=25
person[city]="New York"
# 或者一次性创建
declare -A person=([name]="John" [age]=25 [city]="New York")
访问关联数组
echo ${person[name]} # 输出:John
echo ${person[age]} # 输出:25
# 获取所有键
echo ${!person[@]} # 输出:name age city
# 获取所有值
echo ${person[@]} # 输出:John 25 New York
# 获取元素个数
echo ${#person[@]} # 输出:3
遍历关联数组
declare -A person=([name]="John" [age]=25 [city]="New York")
for key in "${!person[@]}"; do
echo "$key: ${person[$key]}"
done
检查键是否存在
if [[ -v person[name] ]]; then
echo "Key 'name' exists"
fi
# 或者
if [[ ${person[name]+x} ]]; then
echo "Key 'name' exists"
fi
特殊变量
Shell 提供了一系列特殊变量,用于获取脚本和命令的各种信息。
位置参数
位置参数是传递给脚本或函数的参数。
#!/bin/bash
# 文件名:script.sh
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "All arguments: $@"
echo "All arguments: $*"
echo "Number of arguments: $#"
执行 ./script.sh arg1 arg2 arg3 输出:
Script name: ./script.sh
First argument: arg1
Second argument: arg2
All arguments: arg1 arg2 arg3
All arguments: arg1 arg2 arg3
Number of arguments: 3
$@ 和 $* 的区别
当使用双引号包围时,两者有重要区别:
#!/bin/bash
# 测试 "$@" 和 "$*" 的区别
echo 'Using "$@"'
for arg in "$@"; do
echo " Arg: $arg"
done
echo 'Using "$*"'
for arg in "$*"; do
echo " Arg: $arg"
done
执行 ./test.sh "arg1 arg2" arg3 输出:
Using "$@"
Arg: arg1 arg2
Arg: arg3
Using "$*"
Arg: arg1 arg2 arg3
"$@" 保留每个参数的独立性,而 "$*" 将所有参数合并为一个字符串。
shift 命令
shift 命令将位置参数向左移动,$2 变成 $1,$3 变成 $2,以此类推:
#!/bin/bash
while [ $# -gt 0 ]; do
echo "Processing: $1"
shift
done
执行 ./script.sh a b c 输出:
Processing: a
Processing: b
Processing: c
其他特殊变量
| 变量 | 含义 |
|---|---|
$? | 上一个命令的退出状态码 |
$$ | 当前 Shell 进程的 PID |
$! | 最后一个后台命令的 PID |
$- | 当前 Shell 的选项标志 |
$_ | 上一个命令的最后一个参数 |
$RANDOM | 0-32767 之间的随机数 |
$LINENO | 当前行号 |
$BASH_VERSION | Bash 版本 |
$BASH_SOURCE | 当前脚本文件名 |
$FUNCNAME | 当前函数名 |
#!/bin/bash
echo "Current PID: $$"
echo "Random number: $RANDOM"
echo "Bash version: $BASH_VERSION"
# 后台执行命令
sleep 100 &
echo "Background PID: $!"
# 检查上一个命令的退出状态
ls /nonexistent 2>/dev/null
echo "Exit status: $?"
环境变量
环境变量是在 Shell 进程中定义的变量,可以被子进程继承。
查看环境变量
# 查看所有环境变量
env
# 或
printenv
# 查看特定环境变量
echo $HOME
echo $PATH
echo $USER
# 使用 printenv
printenv HOME
printenv PATH
常用环境变量
| 变量 | 含义 |
|---|---|
HOME | 用户主目录 |
PATH | 命令搜索路径 |
USER | 当前用户名 |
PWD | 当前工作目录 |
SHELL | 当前 Shell |
LANG | 系统语言设置 |
TERM | 终端类型 |
EDITOR | 默认编辑器 |
PS1 | 主命令提示符 |
PS2 | 次命令提示符(续行) |
设置环境变量
临时设置(当前 Shell)
export MY_VAR="Hello"
echo $MY_VAR
在脚本中设置
#!/bin/bash
export MY_VAR="Hello"
./child_script.sh # 子脚本可以访问 MY_VAR
在配置文件中设置
将环境变量添加到 ~/.bashrc 或 ~/.bash_profile:
# 在 ~/.bashrc 中添加
export MY_VAR="Hello"
export PATH="$HOME/bin:$PATH"
然后执行 source ~/.bashrc 使其生效。
export 命令
export 命令将变量导出为环境变量,使其能被子进程继承:
# 定义普通变量
var1="Hello"
# 定义并导出环境变量
export var2="World"
# 先定义后导出
var3="Test"
export var3
# 查看导出的变量
export -p
删除环境变量
unset MY_VAR
变量作用域
理解变量作用域对于编写复杂脚本非常重要。
全局变量
在脚本中定义的变量默认是全局的,在整个脚本中都有效:
#!/bin/bash
var="global"
func() {
echo "In function: $var"
}
func
echo "Outside function: $var"
局部变量
使用 local 关键字在函数中定义局部变量:
#!/bin/bash
var="global"
func() {
local var="local"
echo "In function: $var"
}
func
echo "Outside function: $var"
输出:
In function: local
Outside function: global
子 Shell 中的变量
子 Shell 会继承父 Shell 的变量,但子 Shell 中的修改不会影响父 Shell:
#!/bin/bash
var="parent"
(
var="child"
echo "In subshell: $var"
)
echo "After subshell: $var"
输出:
In subshell: child
After subshell: parent
参数扩展高级用法
参数扩展是 Shell 中处理变量的强大工具。
默认值
# 如果变量未定义或为空,使用默认值
echo ${var:-default} # 如果 var 未定义,输出 default
# 如果变量未定义或为空,赋默认值并返回
echo ${var:=default} # 如果 var 未定义,var 被设为 default 并输出
# 如果变量未定义或为空,显示错误信息
echo ${var:?Error: var is not set} # 如果 var 未定义,输出错误并退出
# 如果变量已定义且非空,使用替代值
echo ${var:+alternative} # 如果 var 已定义,输出 alternative
字符串操作总结
str="Hello, World!"
# 长度
${#str} # 13
# 子字符串
${str:7} # World!
${str:0:5} # Hello
${str: -6} # World!
# 删除前缀
${str#Hello, } # World!
${str##*, } # World!
# 删除后缀
${str%World!} # Hello,
${str%%,*} # Hello
# 替换
${str/World/Shell} # Hello, Shell!
${str//o/0} # Hell0, W0rld!
# 大小写
${str^^} # HELLO, WORLD!
${str,,} # hello, world!
变量间接引用
使用 ${!var} 进行间接引用:
name="John"
var="name"
echo ${!var} # 输出:John
变量名前缀匹配
# 获取以特定前缀开头的所有变量名
prefix="BASH"
echo ${!prefix*} # 输出所有以 BASH 开头的变量名
echo ${!prefix@} # 同上
declare 命令
declare 命令用于声明变量并设置属性。
常用选项
| 选项 | 含义 |
|---|---|
-a | 声明数组 |
-A | 声明关联数组 |
-i | 声明整数 |
-r | 声明只读 |
-x | 导出为环境变量 |
-l | 转换为小写 |
-u | 转换为大写 |
-n | 声明名称引用 |
示例
# 声明整数
declare -i num=10
num="hello" # 自动转换为 0
echo $num # 输出:0
# 声明只读变量
declare -r PI=3.14159
# 声明数组
declare -a arr=(1 2 3)
# 声明关联数组
declare -A dict=([key1]="value1" [key2]="value2")
# 声明并导出
declare -x MY_VAR="Hello"
# 自动转小写
declare -l lower="HELLO"
echo $lower # 输出:hello
# 自动转大写
declare -u upper="hello"
echo $upper # 输出:HELLO
# 名称引用
var="target"
declare -n ref=var
ref="modified"
echo $var # 输出:modified
小结
本章详细介绍了 Shell 变量和数据类型:
- 变量定义:等号两边不能有空格,使用
$引用变量 - 数据类型:Shell 变量本质是字符串,但可以声明为整数或数组
- 字符串操作:丰富的参数扩展语法支持字符串截取、替换、删除
- 数组:支持索引数组和关联数组
- 特殊变量:位置参数、退出状态、进程 ID 等系统信息
- 环境变量:使用
export导出,被子进程继承 - 变量作用域:全局变量、局部变量、子 Shell 变量
- 参数扩展:默认值、字符串操作、间接引用等高级用法
掌握变量操作是编写复杂 Shell 脚本的基础。下一章将学习流程控制,包括条件判断和循环语句。