shell 字符串截取(完整指南)

Shell 字符串截取:从入门到实战的完整指南

在日常的 Shell 脚本开发中,我们经常需要从一个完整的字符串中提取出我们关心的部分。比如从文件路径中获取文件名,从日志中提取时间戳,或者从 URL 中分离出域名。这些操作,本质上都是“shell 字符串截取”的应用场景。

对于初学者来说,shell 的字符串操作可能显得有些抽象。但其实,只要掌握几个核心语法,就能轻松应对绝大多数场景。本文将带你一步步理解 shell 字符串截取的底层逻辑,并通过真实案例加深理解。


什么是 shell 字符串截取?

简单来说,shell 字符串截取就是从一个字符串中,按照指定规则取出一部分内容。它不像编程语言中那样有复杂的字符串方法,而是依赖于 Shell 提供的变量扩展语法,特别是 ${} 的用法。

想象一下你有一根长绳子(原始字符串),而你想剪下中间一段(目标子串)。shell 字符串截取,就是帮你“剪绳子”的工具。关键在于,你要清楚知道“从哪开始”、“剪多长”。


基础语法:使用 ${var:offset:length} 截取

这是最常用的截取方式,语法为:

${var:offset:length}
  • var:要操作的变量名
  • offset:从第几个字符开始(从 0 开始计数)
  • length:截取的字符长度(可选)

注意:offset 从 0 开始,与大多数编程语言一致。

示例 1:从第 3 个字符开始,截取 5 个字符

text="Hello World"
result=${text:3:5}
echo "$result"
  • text 值为 "Hello World"
  • offset=3:从第 4 个字符('l')开始
  • length=5:截取 5 个字符 → "llo W"
  • 输出结果:llo W

这就像你用尺子量了 3 厘米,再剪下 5 厘米的布料。

示例 2:只指定起始位置,不指定长度

text="Welcome to Shell Scripting"
result=${text:7}
echo "$result"
  • offset=7:从第 8 个字符开始('t')
  • 没有指定 length,表示从该位置一直截取到末尾
  • 输出结果:to Shell Scripting

这相当于你从某个位置开始,一直剪到底,不关心长度。


前缀与后缀截取:利用 #% 操作符

除了按位置截取,我们还常需要“去掉开头或结尾的某些字符”。shell 提供了 #% 操作符来实现。

1. 去掉最短匹配的前缀:${var#pattern}

filename="/home/user/docs/report.txt"
result=${filename#*/}
echo "$result"
  • # 表示从开头匹配
  • */ 是模式,表示“以 / 结尾的任意前缀”
  • ${filename#*/} 会去掉最短匹配的 / 前面部分
  • 输出结果:home/user/docs/report.txt

举个生活例子:你有一串钥匙,钥匙串上有多个挂件。# 就像“去掉最短的那个挂件”,只留下剩下的。

2. 去掉最长匹配的前缀:${var##pattern}

path="/usr/local/bin/java"
result=${path##*/}
echo "$result"
  • ## 表示最长匹配
  • */ 从开头匹配,直到最后一个 /
  • 去掉最长的路径前缀,只保留文件名
  • 输出结果:java

用钥匙串比喻:## 是“去掉所有挂件”,只留下最核心的那把钥匙。

3. 去掉最短匹配的后缀:${var%pattern}

file="document.pdf.bak"
result=${file%.*}
echo "$result"
  • % 表示从结尾匹配
  • .* 是模式,匹配“以 . 开头的任意后缀”
  • 去掉最短的后缀(.bak),保留 document.pdf
  • 输出结果:document.pdf

4. 去掉最长匹配的后缀:${var%%pattern}

filename="app.log.2024-05-01.gz"
result=${filename%%.*}
echo "$result"
  • %% 表示最长匹配
  • .* 从结尾开始匹配,直到第一个 .(因为是最大匹配)
  • 去掉所有后缀,只保留 app.log.2024-05-01
  • 输出结果:app.log.2024-05-01

比喻:%% 是“从后往前,一直剪,直到剪不到为止”。


实战案例:从 URL 中提取域名

我们来一个真实场景:从一个完整的 URL 中提取域名。

url="https://www.example.com:8080/api/v1/user"
domain=${url#*://}
domain=${domain%%/*}
echo "域名是:$domain"
  • 第一步:#*:// 去掉 https://,得到 www.example.com:8080/api/v1/user
  • 第二步:%%/* 去掉从 / 开始的所有内容,只保留 www.example.com
  • 输出结果:域名是:www.example.com

这个例子展示了 shell 字符串截取的强大之处:通过多步组合,轻松处理复杂文本。


实用技巧:变量拼接与截取结合

在 Shell 脚本中,截取常与变量赋值、条件判断结合使用。

案例:判断文件扩展名

filename="install.sh"
extension=${filename##*.}

if [[ "$extension" == "sh" ]]; then
    echo "这是一个 Shell 脚本"
elif [[ "$extension" == "py" ]]; then
    echo "这是一个 Python 脚本"
else
    echo "未知类型文件"
fi
  • ##*.:去掉文件名中最后一个 . 之前的部分,只保留后缀
  • extension 变量值为 sh
  • 通过 if 判断扩展名,实现文件类型识别

这种方式在自动化脚本中非常常见,比如批量处理脚本、日志分析等。


常见陷阱与注意事项

1. 空变量的处理

如果变量为空,截取操作不会报错,但结果也是空。

var=""
echo "${var:0:5}"  # 输出空,不会报错

建议在使用前判断变量是否为空:

if [[ -z "$var" ]]; then
    echo "变量为空"
else
    result=${var:3:10}
fi

2. offset 超出字符串长度

如果 offset 大于字符串长度,结果为空。

text="abc"
echo "${text:10:2}"  # 输出空

3. 使用 #% 时注意模式匹配

#% 后面是模式匹配,不是纯字符串。例如:

file="test.tar.gz"
echo "${file#*.}"     # 输出 "tar.gz"(匹配第一个 .)
echo "${file##*.}"    # 输出 "gz"(匹配最后一个 .)

### 的区别,正是“最短匹配”与“最长匹配”的体现。


总结:掌握核心,灵活组合

shell 字符串截取看似简单,实则蕴含强大能力。通过掌握以下四类核心语法,你就能应对绝大多数场景:

操作方式 语法 用途
按位置截取 ${var:offset:length} 精确控制起始位置和长度
去最短前缀 ${var#pattern} 去掉开头最短匹配的部分
去最长前缀 ${var##pattern} 去掉开头最长匹配的部分
去最短后缀 ${var%pattern} 去掉结尾最短匹配的部分
去最长后缀 ${var%%pattern} 去掉结尾最长匹配的部分

真正的高手,不在于记住所有语法,而在于懂得组合使用。比如先用 ##*. 提取后缀,再用 :0:3 截取前 3 个字符,实现“提取文件扩展名前 3 位”。

在日常开发中,shell 字符串截取是自动化脚本的基石。无论是日志分析、路径处理,还是配置文件解析,它都发挥着不可替代的作用。建议你动手写几个小脚本,把今天学到的技巧用起来。

记住:语法是工具,理解才是关键。多练习,多调试,你会越来越熟练。