如何在shell中反转printf %q操作,将其输出还原为原始参数数组
反转
printf %q 输出为原始参数数组的实现方案 基础说明
GNU coreutils 或 Bash 内置的 printf %q 命令可以对包含空格、换行符、特殊引号的参数做转义引用,保证转义后的字符串可以直接作为shell输入使用,示例如下:
$ main() { printf '%q ' "${@}"; } && main "'a'" '"b"' 'c d' $'e\nf' \'a\' \"b\" c\ d $'e\nf'
以下针对将printf %q生成的转义字符串还原为原始参数数组的需求,分不同使用场景给出可落地的实现方案。
场景1:POSIX Shell 环境实现
纯POSIX Shell 不支持数组,也没有提供原生的shell词法解析接口,同时要求不能使用eval的前提下,无法实现全场景兼容,仅能处理无ANSI C风格转义、无嵌套特殊字符的简单场景:
# 仅适配无换行、无ANSI转义(如$'\n')的简单场景 posix_unquote() { # 解析结果直接存入位置参数 $@ set -- $(echo "$1" | sed 's/\\ /\x01/g' | tr ' ' '\n' | sed 's/\x01/ /g; s/\\\([\'"]\)/\1/g') # 测试输出 echo "解析得到 $# 个参数:" n=1 for arg in "$@"; do echo "$n: <$arg>" n=$((n+1)) done }
如果需要在POSIX环境下处理复杂场景,必须借助外部工具实现。
场景2:Bash 环境实现
Bash 4.4及以上版本可以用原生特性实现全场景兼容,完全不需要使用eval:
bash_unquote() { local quoted_str="$1" local -a args # 利用read的词解析能力拆分转义字符串,支持换行、特殊引号 while IFS= read -r -d '' item; do args+=("$item") done < <(xargs -0 printf '%s\0' <<< "$quoted_str") # 测试输出 echo "解析得到 ${#args[@]} 个参数:" for i in "${!args[@]}"; do echo "$((i+1)): <${args[$i]}>" done }
如果可以接受使用可信输入下的eval(输入仅为printf %q生成的转义字符串时完全安全),可以用更简洁的写法:
# 仅适用于输入是printf %q生成的可信场景 eval "args=($quoted_str)"
场景3:可借助额外工具实现
允许调用外部工具时,推荐使用Python的shlex模块实现全场景兼容,支持任意复杂参数包括换行、嵌套转义字符串等极端情况:
python_unquote() { local quoted_str="$1" local -a args # 用空字符作为分隔符传递参数,避免任何字符冲突 while IFS= read -r -d '' arg; do args+=("$arg") done < <(python3 -c ' import shlex, sys for arg in shlex.split(sys.stdin.read()): sys.stdout.buffer.write(arg.encode("utf8") + b"\x00") ' <<< "$quoted_str") # 测试输出 echo "解析得到 ${#args[@]} 个参数:" for i in "${!args[@]}"; do echo "$((i+1)): <${args[$i]}>" done }
该方案跨shell兼容性好,性能可以满足绝大多数生产场景需求。
补充说明
- 原生
xargs默认无法处理带引号的换行符,必须搭配-0参数使用空字符作为分隔符才能支持 - Zsh原生支持该需求:对于存放在变量
quoted_str中的转义字符串,直接使用"${(Q@)${(z)quoted_str}}"语法即可完成还原
内容的提问来源于stack exchange,提问作者Christian
相关产品推荐
相关产品推荐

