Bash参数拆分需求:按空格分割且保留引号的可移植实现
问题描述
给定Bash参数:
foo='ab "cd" "e f" x="1 2" '
需要将其转换为等价于以下定义的数组:
foo_transformed=( ab '"cd"' '"e f"' 'x="1 2"' )
要求实现方案满足:
- 可移植性:仅使用Bash v3+内置命令或Linux、Unix、Cygwin默认自带程序
- 安全性与准确性:适配任意空白字符(分隔符或双引号内的空白),保留原字符串中的双引号
- 已知输入限制:不含单引号
'或反斜杠\
此前尝试的两种方法均无法满足需求:
直接赋值:
foo_transformed=( $foo )无法识别双引号作为分组标记,输出结果会将引号内的空白也当作分隔符:
- ab - "cd" - "e - f" - x="1 - 2"使用
eval:eval foo_transformed=( $foo )会解析并丢弃双引号,导致元素内容丢失引号:
- ab - cd - e f - x=1 2
解决方案
通用方案(基于GNU awk)
使用awk的FPAT特性(GNU awk 3.1.5+支持,多数系统默认自带)准确定义字段规则,结合Bash循环读取构建数组:
foo_transformed=() while IFS= read -r elem; do foo_transformed+=("$elem") done < <(awk 'BEGIN { FPAT = "([^[:space:]]+)|(\"[^\"]+\")" } { for (i=1; i<=NF; i++) print $i }' <<<"$foo")
验证结果
执行遍历命令:
for k in "${foo_transformed[@]}"; do echo "- $k"; done
输出符合预期:
- ab - "cd" - "e f" - x="1 2"
原理说明
FPAT = "([^[:space:]]+)|(\"[^\"]+\")":定义两个匹配规则,要么是不含空白的连续字符,要么是被双引号包裹、内部不含双引号的字符串,精准区分分隔空白与引号内的空白。- awk遍历匹配到的字段逐行输出,Bash循环读取每一行作为数组元素,完整保留原内容(包括双引号)。
兼容BSD awk方案(如macOS默认环境)
若系统默认使用BSD awk(不支持FPAT),可改用自定义分割逻辑实现:
foo_transformed=() while IFS= read -r elem; do foo_transformed+=("$elem") done < <(awk '{ str = $0 while (length(str) > 0) { if (substr(str, 1, 1) == "\"") { end = index(substr(str, 2), "\"") + 1 elem = substr(str, 1, end) str = substr(str, end + 1) } else { end = match(str, /[[:space:]]/) if (end == 0) { elem = str str = "" } else { elem = substr(str, 1, end - 1) str = substr(str, end + 1) } } gsub(/^[[:space:]]+|[[:space:]]+$/, "", elem) if (length(elem) > 0) print elem } }' <<<"$foo")
内容的提问来源于stack exchange,提问作者Zorawar
相关产品推荐
相关产品推荐

