PAYATHON 2026

Bash 如何像 Python `.split()` 一样按多字符分隔符拆分并存入数组?

技术老齐

Bash 没有直接对应 str.split() 的内建函数,可以用参数展开逐次查找多字符分隔符,再把每段字符串追加到数组中。对于不含 NUL 字节的文本,这种写法能保留换行、空格和空元素,实现类似 Python text.split('dab') 的效果。

为什么不能直接设置 IFS

IFS 会把其中的每个字符分别当作分隔符。例如:

IFS='dab'

这表示按 d、a、b 中的任意字符分隔,无法把完整的 dab 当作一个分隔符。配合 read -a 使用也是如此。

read -d 'dab' 同样不适用,因为 -d 只使用参数的第一个字符作为结束符,这里实际使用的是 d。

用参数展开拆分并存入数组

这段代码可以直接在 Bash 中运行,无需外部工具:

#!/usr/bin/env bash

text='

dab
a
b
c
dab
e
f
g
dab
h
i
j
'

sep='dab'  # 分隔符必须非空
rest=$text
parts=()

while [[ $rest == *"$sep"* ]]; do
    parts+=( "${rest%%"$sep"*}" )
    rest=${rest#*"$sep"}
done

# 保存最后一段,即使它是空字符串
parts+=( "$rest" )

# 查看数组内容,%q 会把换行等字符显示为转义形式
for i in "${!parts[@]}"; do
    printf 'parts[%s]=%q\n' "$i" "${parts[$i]}"
done

输出结果:

parts[0]=$'\n\n'
parts[1]=$'\na\nb\nc\n'
parts[2]=$'\ne\nf\ng\n'
parts[3]=$'\nh\ni\nj\n'

数组中保存的仍是真实换行,$'\n...' 只是 printf '%q' 将其显示出来的形式。

循环每次处理剩余文本中的第一个分隔符:

  • [[ $rest == *"$sep"* ]] 判断剩余文本是否包含分隔符。
  • ${rest%%"$sep"*} 删除匹配 "$sep"* 的最长后缀,得到第一个分隔符之前的内容。
  • ${rest#*"$sep"} 删除匹配 *"$sep" 的最短前缀,留下第一个分隔符之后的内容。

保留 "$sep" 两侧的引号,才能按字面值匹配分隔符。这样,即使分隔符含有 *、?、[ 等模式字符,也不会把它们当作通配符。

是否会保留空元素

对于非空分隔符,这段代码会保留空元素,行为与 Python 显式指定分隔符时一致:

textsep数组元素
abcdab"abc"
空字符串dab""
dabXdab""、"X"
Xdabdab"X"、""
XdabdabYdab"X"、""、"Y"

最后的 parts+=( "$rest" ) 不能省略,否则会丢掉最后一段,也无法保留末尾分隔符产生的空元素。

文本中任意位置出现的 dab 都会参与拆分。例如,XdabY 会拆成 "X" 和 "Y",与 Python .split('dab') 一致。

输入来自命令的 stdout 时

如果不需要保留输出末尾的换行,可以先捕获输出,再执行上面的拆分代码:

text=$(your_command)

命令替换会删除输出末尾的所有换行符。需要保留这些换行时,可以改用:

IFS= read -r -d '' text < <(your_command) || :

将 your_command 替换为实际命令,然后执行从 sep='dab' 开始的拆分代码。

其中,IFS= 保留首尾空白,-r 保留反斜杠,-d '' 以 NUL 为结束符。普通文本没有 NUL,read 会一直读到 EOF,并返回非零状态,末尾的 || : 用来接受这种情况。这种写法只负责收集输出;如果还要判断生成输出的命令是否成功,需要单独处理它的退出状态。

使用时注意

分隔符必须非空,否则循环无法推进。Bash 变量不能保存 NUL 字节,因此这套方法适用于普通文本,不适合任意二进制数据。

读取或遍历结果时,也要保留双引号:

printf '%s' "${parts[1]}"

for part in "${parts[@]}"; do
    printf '[%s]\n' "$part"
done

"${parts[@]}" 会把每个数组元素作为独立参数传递,保留其中的空格、换行和空元素。代码使用了 Bash 数组和 [[ ... ]],需要通过 Bash 执行,例如 bash script.sh。

备注:内容仅供参考。