Bash 字符串处理
处理字符串是 Bash 脚本编写中极其基础且重要的一环。字符串本质上是字符的序列,操作它们可以让你处理文本数据、格式化终端输出以及构建高度动态的脚本。本章将全面涵盖基本的字符串操作,包括如何定义、访问、修改(截取/替换)以及比较字符串,为你提供在脚本中高效处理基于文本的数据所需的硬核技能。
1. 字符串字面量与变量
1.1 定义字符串字面量
在 Bash 中,字符串字面量是用单引号或双引号括起来的字符序列。
- 单引号 (
'...'): 严格保留引号内每个字符的字面值(所见即所得)。在单引号内,任何变量替换(如$var)和命令执行(如`date`)都不会发生。
string1='这是一个纯字符串'
echo $string1 # 输出: 这是一个纯字符串
string2='变量 x 的值是: $x'
echo $string2 # 输出: 变量 x 的值是: $x (注意:$x 没有被解析替换)双引号 ("..."):允许进行变量替换和命令执行。诸如 $(用于标记变量)和反引号 `(用于命令替换)等特殊字符会被 Bash 解析并执行。
x=10
string3="变量 x 的值是: $x"
echo $string3 # 输出: 变量 x 的值是: 10
string4="今天是 $(date +%A)" # 推荐使用 $() 代替反引号
echo $string4 # 输出: 今天是 [星期几]1.2 字符串赋值与访问
使用等号 = 将字符串字面量赋给变量。切记:等号周围不能有任何空格。
name="John Doe"
message="你好, $name!"
echo $message # 输出: 你好, John Doe!要访问字符串变量的值,请在变量名前加上 $,或者使用 ${variable_name}。后者在变量名紧接着其他字母或数字时非常有用,可以消除 Bash 解析变量名边界的歧义。
name="Jane"
echo $name # 输出: Jane
echo ${name}Smith # 输出: JaneSmith (如果写成 $nameSmith,Bash 会去找名为 nameSmith 的变量)2. 字符串高级操作
Bash 提供了一系列强大的内置参数扩展语法,让你无需调用诸如 awk 或 sed 等外部命令即可完成大多数日常字符串处理。
2.1 获取字符串长度
使用 ${#variable_name} 可以获取字符串的字符长度。
str="Hello"
length=${#str}
echo $length # 输出: 52.2 子串提取 (切片)
你可以使用 ${variable_name:offset:length} 语法从一个长字符串中提取一段子串。
offset(偏移量): 子串的起始位置(索引从 0 开始计数)。length(长度): 要提取的字符数量。如果省略此参数,将提取从offset一直到字符串末尾的所有内容。
str="Bash Scripting"
substring1=${str:0:4} # 从索引 0 开始,提取 4 个字符
echo $substring1 # 输出: Bash
substring2=${str:5} # 从索引 5 开始,提取到末尾
echo $substring2 # 输出: Scripting
substring3=${str:5:6} # 从索引 5 开始,提取 6 个字符
echo $substring3 # 输出: Script2.3 基于模式匹配删除子串 (掐头去尾)
Bash 提供了一种基于通配符模式,从字符串的开头(头部)或结尾(尾部)删除子串的巧妙方法,常用于处理文件路径和扩展名。
${variable#pattern}(掐短头): 从字符串的开头删除与pattern匹配的最短部分。${variable##pattern}(掐长头): 从字符串的开头删除与pattern匹配的最长部分。${variable%pattern}(去短尾): 从字符串的结尾删除与pattern匹配的最短部分。${variable%%pattern}(去长尾): 从字符串的结尾删除与pattern匹配的最长部分。
file="path/to/my/file.txt.bak"
# 掐短头 (最短的前缀匹配)
short_prefix=${file#*/}
echo $short_prefix # 输出: to/my/file.txt.bak (只删除了 path/)
# 掐长头 (最长的前缀匹配) -> 经典应用:提取纯文件名
long_prefix=${file##*/}
echo $long_prefix # 输出: file.txt.bak (删除了 path/to/my/)
# 去短尾 (最短的后缀匹配) -> 经典应用:去除最后一个扩展名
short_suffix=${file%.*}
echo $short_suffix # 输出: path/to/my/file.txt (只删除了 .bak)
# 去长尾 (最长的后缀匹配) -> 经典应用:去除所有扩展名
long_suffix=${file%%.*}
echo $long_suffix # 输出: path/to/my/file (删除了 .txt.bak)2.4 字符串替换
你可以使用参数扩展在字符串中替换特定的内容。
${variable/pattern/replacement}: 将第一个匹配到的pattern替换为replacement。${variable//pattern/replacement}: 将所有匹配到的pattern替换为replacement。
str="apple banana apple"
# 替换第一个匹配项
replaced1=${str/apple/orange}
echo $replaced1 # 输出: orange banana apple
# 替换所有匹配项 (注意双斜杠 //)
replaced2=${str//apple/orange}
echo $replaced2 # 输出: orange banana orange2.5 大小写转换
Bash 允许你轻松地转换字符串的大小写。
(注意:原生的大写 ^^ 和小写 ,, 语法仅在 Bash 4.0 或更高版本中可用。如果你使用的是极老的 macOS 默认 Bash,可能需要升级或使用备用方案。)
- 转换为大写:
${variable^^} - 转换为小写:
${variable,,}
str="Hello World"
# 转换为大写
uppercase=${str^^}
echo $uppercase # 输出: HELLO WORLD
# 转换为小写
lowercase=${str,,}
echo $lowercase # 输出: hello world备用方案 (适用于旧版 Bash):使用 tr 命令
str="Hello World"
uppercase=$(echo "$str" | tr '[:lower:]' '[:upper:]')
echo $uppercase # 输出: HELLO WORLD3. 字符串对比
3.1 检查相等 (= 和 ==)
在 Bash 的条件测试语句 [ ] 或 [[ ]] 中,单个等号 = 和双等号 == 都可用于检查两个字符串的内容是否完全相同。
str1="hello"
str2="world"
# 推荐使用双中括号 [[ ]]
if [[ "$str1" == "$str2" ]]; then
echo "字符串相等"
else
echo "字符串不相等" # 输出: 字符串不相等
fi3.2 检查不相等 (!=)
!= 运算符用于检查两个字符串是否不相等。
str1="hello"
str2="world"
if [[ "$str1" != "$str2" ]]; then
echo "字符串确实不相等" # 将会输出此行
fi3.3 字典序/按字母顺序比较 (<, >)
你可以使用 < 和 > 根据字母表的顺序(字典序)比较字符串。
极其重要的警告: 这与数字比较(-lt, -gt)的逻辑完全不同。
str1="apple"
str2="banana"
# 必须使用 [[ ]] 才能正确解析 < 和 > 作为比较符,而不是重定向符
if [[ "$str1" < "$str2" ]]; then
echo "在字典中,apple 排在 banana 前面" # 将会输出此行
fi核心最佳实践提示:
- 在执行字符串比较时,务必用双引号把变量括起来(如
"$str1"),以防止当变量为空或包含空格时引发可怕的语法错误。 - 在现代 Bash 脚本中,进行字符串条件判断时,强烈推荐使用
[[ ]]而不是旧式的[ ]。[[ ]]更安全,支持模式匹配,并且不需要对<和>等符号进行繁琐的反斜杠转义。
4. 实战案例演示
案例 1:快速验证用户输入是否为空
在编写交互式脚本时,经常需要确保用户没有直接按回车跳过输入。我们可以使用 -z(Zero length,长度为零)或 -n(Non-zero length,长度非零)来快速检查。
read -p "请输入您的密码: " user_password
if [[ -z "$user_password" ]]; then
echo "错误:密码不能为空!"
exit 1
fi
echo "密码已接收。"案例 2:使用正则表达式验证输入格式
如果你使用双中括号 [[ ]],你可以使用 =~ 运算符将字符串与正则表达式 (Regular Expressions) 进行匹配。这是极其强大的验证功能。
read -p "请输入一个合法的用户名 (仅限字母和数字): " username
# ^[a-zA-Z0-9]+$ 表示从头到尾只能由至少一个英文字母或数字组成
if [[ ! "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
echo "验证失败:用户名只能包含字母和数字组合。"
exit 1
fi
echo "用户名 '$username' 格式验证通过。"案例 3:批量修改文件扩展名
假设你想将当前目录下所有以 .old 结尾的文件批量重命名为 .new。这就用到了我们前面学到的“去尾”操作。
for file in *.old; do
# 检查是否真的找到了文件(防止目录下没有 .old 文件时发生错误)
if [[ -f "$file" ]]; then
newfile="${file%.old}.new" # 核心操作:截掉 .old,拼上 .new
mv "$file" "$newfile"
echo "已将文件重命名: $file -> $newfile"
fi
done