处理日志、配置文件或命令输出时,按分隔符把一个字符串切成几段是最常见的操作之一。Shell 下能做这件事的工具不少:cut、awk、IFS、参数展开、tr 各有适用场景,本文逐一给出示例。
以下示例统一使用这个字符串:
str="192.168.1.10,root,22"
cut:按单字符分隔符取列
cut 最简单,-d 指定分隔符(只能是单个字符),-f 指定取第几列:
echo "$str" | cut -d',' -f1 # 输出 192.168.1.10
echo "$str" | cut -d',' -f2 # 输出 root
echo "$str" | cut -d',' -f1,3 # 输出 192.168.1.10,22
echo "$str" | cut -d',' -f2- # 输出 root,22(第 2 列到结尾)
awk -F:更灵活的取列
awk 用 -F 指定分隔符,支持多字符和正则,还能在取列的同时做进一步处理:
echo "$str" | awk -F',' '{print $1}' # 输出 192.168.1.10
echo "$str" | awk -F',' '{print $2}' # 输出 root
echo "$str" | awk -F',' '{print NF}' # 输出 3,NF 是字段个数
echo "$str" | awk -F',' '{print $1 " " $2}' # 输出 192.168.1.10 root
-F 支持正则,比如按一个或多个空白字符分割:
echo "a b c" | awk -F' +' '{print $2}' # 输出 b
IFS:让 Shell 原生拆分
IFS(Internal Field Separator)是 Shell 的内部字段分隔符变量。临时把它改成目标分隔符,再用 read 拆到多个变量里,是纯 Shell 的经典写法:
IFS=',' read -r ip user port <<< "$str"
echo "$ip" # 输出 192.168.1.10
echo "$user" # 输出 root
echo "$port" # 输出 22
IFS=',' read ...:把 IFS 的修改限定在这条命令内,不会污染当前 Shell;<<<是 Here String,把字符串作为read的标准输入;-r防止反斜杠被转义。
分割目录路径(按 : 分隔)也常用这种思路:
IFS=':' read -r dir1 dir2 dir3 <<< "$PATH"
参数展开:不启动子进程的最快方式
利用 ${var#pattern}、${var%pattern} 等参数展开,可以不产生任何子进程就完成切分,在循环里处理大量数据时性能最好:
| 写法 | 含义 |
|---|---|
${var#*/} |
从头删除最短匹配 */ 的部分(删掉第一个分隔符及之前的内容) |
${var##*/} |
从头删除最长匹配 */ 的部分(删掉最后一个分隔符及之前的内容) |
${var%/*} |
从尾删除最短匹配 /* 的部分(删掉最后一个分隔符及之后的内容) |
${var%%/*} |
从尾删除最长匹配 /* 的部分(删掉第一个分隔符及之后的内容) |
示例:
path="/usr/local/bin/ls"
echo ${path#*/} # 输出 usr/local/bin/ls
echo ${path##*/} # 输出 ls(等价于 basename)
echo ${path%/*} # 输出 /usr/local/bin(等价于 dirname)
echo ${path%%/*} # 输出空字符串
# 取第一个逗号前的内容
echo ${str%%,*} # 输出 192.168.1.10
# 取第一个逗号后的内容
echo ${str#*,} # 输出 root,22
# 取最后一个逗号后的内容
echo ${str##*,} # 输出 22
配合循环可以逐段弹出:
list="a,b,c"
while [ -n "$list" ]; do
echo "${list%%,*}"
list=${list#*,}
done
# 依次输出 a b c(最后一段没有逗号时,list 整体作为末项输出)
tr:把分隔符”压平”后交给其他命令
tr 本身不做字段切分,它擅长字符替换。把分隔符统一替换成空格或换行后,往往就能直接配合其他工具使用了:
# 逗号转空格,配合 for 循环遍历
for item in $(echo "$str" | tr ',' ' '); do
echo "$item"
done
# 依次输出三行:192.168.1.10 / root / 22
# 逗号转换行,每行一个字段
echo "$str" | tr ',' '\n'
小结
- 临时取一两列:
cut最省事; - 需要正则分隔符或对字段加工:用
awk -F; - 纯 Shell、要拆进多个变量:用
IFS+read; - 性能敏感、只需取头尾字段:用参数展开
${var%%/*}、${var#*/}等; - 分隔符不统一、想先归一化:用
tr转换后再处理。
根据”分隔符是否固定、字段取在哪头、是否在意性能”来选,基本就能覆盖日常所有场景。