Bash printf 格式串:文本里的百分号为什么不能直接交给第一个参数

10-01 3阅读

脚本读取了一段带百分号的文本,原样保存没有问题,交给 printf 却少了字符,甚至报格式错误。给变量加双引号仍然无效,是因为问题不在分词,而在参数的角色:printf 的格式参数会被解释,其中的百分号与反斜杠拥有特殊含义。

稳妥的文本输出形式是 printf 的格式由脚本固定,外来文本放到后面的数据参数。比如格式使用 %s,表示把对应参数当作字符串输出;需要换行时,把换行写在受控格式里,而不要让数据决定格式规则。

比较两个都加了引号的调用

以下脚本用 Bash 5.2.37 的内建 printf 验证,保存为 demo.sh,再用 bash demo.sh 运行。printf -v 把结果保存到变量,方便用相等断言直接验证,不经过命令替换,也就不会因为命令替换删除末尾换行而干扰实验。

Bash printf 格式串:文本里的百分号为什么不能直接交给第一个参数

AI概念示意图:用抽象图形说明本文主题,不代表真实界面或运行结果。

#!/usr/bin/env bash
set -euo pipefail
payload='item=%s\n'
builtin printf -v wrong "$payload" 'changed'
builtin printf -v right '%s' "$payload"
[[ $wrong == $'item=changed\n' ]]
[[ $right == "$payload" ]]
builtin printf 'wrong=%q right=%q\n' "$wrong" "$right"
escaped='A\nB'
builtin printf -v kept '%s' "$escaped"
builtin printf -v expanded '%b' "$escaped"
[[ $kept == 'A\nB' ]]
[[ $expanded == $'A\nB' ]]
builtin printf 'kept=%q expanded=%q\n' "$kept" "$expanded"
builtin printf -v percent 'progress=%d%%' 50
[[ $percent == 'progress=50%' ]]
builtin printf '%s\n' "$percent"

引号负责参数边界,格式仍由 printf 解释

payload 里的 %s 和反斜杠加 n 原本都是普通字符。wrong 调用把它放在格式位置,%s 消耗后面的 changed,反斜杠加 n 变成真实换行;right 调用把固定的 %s 放在格式位置,payload 就完整保留下来。两处变量都用了双引号,差别完全来自传给 printf 的角色。

第一行用 %q 打印观察结果,因此换行显示成可辨认的引用形式。%q 适合帮助观察 Bash 参数,不是通用文件序列化方案,也不是让你随后用 eval 重新执行这段文本。常规输出给读者或写入文本文件,仍按实际用途选择固定格式。

%s 与 %b 也不能混用

第二组数据是三个组成部分:A、字面反斜杠加 n、B。%s 保留它,%b 则把反斜杠序列解释成换行。只有业务明确接收一套转义表示法时,才应该使用 %b;如果只是日志、名称或用户留言,随意展开会改变原始数据。

固定格式里的 %% 表示一个字面百分号,代码最后验证 progress=50%。但通过 %s 传入的数据无需先把所有百分号改成两个。提前替换再用 %s 会把双百分号真实写出去,反而造成二次处理错误。

把格式固定以后,仍要考虑目的地

%s 只保证 printf 不把数据里的格式字符再次解释,它不会替你转义 CSV 引号、JSON 控制字符或 HTML 标签,也不会把换行变成安全的日志分隔。如果数据将进入这些结构,应交给对应格式的编码器,而不是把一个输出函数当作通用转义器。

数据里已有真实换行时,固定的 %s 仍会原样输出,因此一条输入可能占据多行。终端控制字符也需要按日志展示策略处理。需要严格一条记录一行的日志,应另行定义编码或限制,并用含换行的样本验证记录边界。

printf 还会按参数数量重复使用格式,缺少参数也不总是报错。因此只检查命令返回零,不能证明字段个数正确。输出固定结构时,应在调用前核对记录形状;本文用字符串相等断言,验证的是字节内容是否符合预期。

排查时先确认使用的是 Bash 内建实现,因为系统外部 printf 与其他 shell 的扩展选项未必相同。本文通过 builtin 明确选择实现,使用的 %q 和 -v 尤其不应未经核对就搬进要求严格可移植的 sh 脚本。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。