如何在Bash中用printf实现含Unicode/终端装饰的字符串对齐?
解决Bash中含Unicode宽字符或颜色代码的文本列对齐问题
核心问题原因
Bash内置的printf "%-Ns"是按字节数计算宽度,而非终端显示的可视列数:
- 多字节Unicode宽字符(如
★)占2字节但终端显示占2列,普通Unicode字符占多字节但显示占1列,printf会误算为字节数。 - 终端装饰代码(如ANSI颜色序列)是不可见的控制字符,
printf会将其计入宽度,导致实际可视文本缩进不足。
完美解决方案(支持颜色代码+Unicode宽字符)
使用Perl的Unicode::GCString模块,该模块可以准确计算字符串的终端可视列数,同时剥离不可见的ANSI控制码。
步骤1:安装依赖
Debian/Ubuntu系:
sudo apt install libunicode-linebreak-perl
RHEL/CentOS系:
sudo yum install perl-Unicode-LineBreak
步骤2:定义对齐函数
在Bash脚本中添加以下函数:
# 计算文本的终端可视列数(剥离ANSI控制码,处理宽字符) get_visible_length() { perl -MUnicode::GCString -e ' $str = do { local $/; <STDIN> }; $str =~ s/\e\[[0-9;]*m//g; # 移除所有ANSI SGR控制序列 $gc = Unicode::GCString->new($str); print $gc->columns(), "\n"; ' <<< "$1" } # 左对齐文本到指定宽度 align_left() { local text="$1" local target_width="$2" local actual_length=$(get_visible_length "$text") local pad_length=$(( target_width - actual_length )) # 确保填充长度非负 if [ $pad_length -lt 0 ]; then pad_length=0 fi printf "%s%s" "$text" "$(printf "%${pad_length}s" "")" }
步骤3:测试使用
# 定义颜色输出函数 printred () { tput setaf 1; printf %b "$*"; tput sgr0; } # 测试对齐效果 align_left "test" 20 ; echo " NOK" align_left "$(printred RED)" 20 ; echo " NOK" align_left "★★★★" 20 ; echo " NOK"
输出结果(完美对齐):
test NOK RED NOK ★★★★ NOK
轻量化方案(仅支持颜色代码,不处理宽字符)
如果不需要处理Unicode宽字符,可使用sed剥离控制码后用wc计算字符数,无需额外安装模块:
get_visible_length_light() { echo -n "$1" | sed 's/\x1b\[[0-9;]*m//g' | wc -m } align_left_light() { local text="$1" local target_width="$2" local actual_length=$(get_visible_length_light "$text") local pad_length=$(( target_width - actual_length )) [ $pad_length -lt 0 ] && pad_length=0 printf "%s%s" "$text" "$(printf "%${pad_length}s" "")" }
原理说明
get_visible_length函数:先用正则移除所有ANSI颜色控制码(\e\[[0-9;]*m匹配终端样式控制序列),再通过Unicode::GCString的columns()方法获取字符串在终端中实际占用的列数,该方法会自动识别宽字符(如东亚字符、emoji等)的列宽。align_left函数:计算目标宽度与实际可视宽度的差值,补充对应数量的空格,实现左对齐。
内容的提问来源于stack exchange,提问作者Popup
相关产品推荐
相关产品推荐

