当尝试格式化printf
涉及包含多字节字符的字符串的输出时,很明显,它printf
不计算文字字符,而是计算字节数,如果混合使用单字节和多字节字符,这会使格式化文本变得困难。例如:
$ cat script
#!/bin/bash
declare -a a b
a+=("0")
a+=("00")
a+=("000")
a+=("0000")
a+=("00000")
b+=("0")
b+=("├─00")
b+=("├─000")
b+=("├─0000")
b+=("└─00000")
printf "%-15s|\n" "${a[@]}" "${b[@]}"
$ ./script
0 |
00 |
000 |
0000 |
00000 |
0 |
├─00 |
├─000 |
├─0000 |
└─00000 |
我找到了各种建议的解决方法(主要是使用另一种语言或实用程序来打印文本的包装器)。有没有原生的 bash 解决方案?没有任何记录printf
格式化字符串似乎有帮助。在这种情况下,设置是否locale
相关,例如使用 UTF-32 等固定宽度字符编码?
答案1
您可以通过告诉终端将光标移动到所需位置来解决这个问题,而不是计算printf
字符数:
$ printf "%s\033[10G-\n" "abc" "├─cd" "└──ef"
abc -
├─cd -
└──ef -
好吧,假设您要打印到终端,那就是......
控制序列在那里<ESC>[nnG
nn
是要移动到的列,以十进制表示。
当然,如果第一列比分配的空间长,结果就不太好:
$ printf "%s\033[10G-\n" "abcdefghijkl"
abcdefghi-kl
要解决此问题,您可以<ESC>[K
在打印以下列之前显式清除该行的其余部分 ( )。
$ printf "%s\033[10G\033[K-\n" "abcdefghijkl"
abcdefghi-
另一种方法是手动进行填充,假设我们有可以确定字符串字符长度的东西。这似乎在 Bash 中适用于简单字符,但当然有点难看。零宽度和双宽度字符可能会破坏它,而且我也没有测试组合字符。
#!/bin/bash
pad() {
# parameters:
# 1: name of variable to pad
# 2: length to pad to
local string=${!1}
local len=${#string}
printf -v "$1" "%s%$(($2 - len))s" "$string" ""
}
echo "1234567890"
for x in "abc" "├─cd" "└──ef" ; do
pad x 9
printf "%s-\n" "$x"
done
输出是:
1234567890
abc -
├─cd -
└──ef -
答案2
这是一个使用的解决方案wc -L
。
for i in "${a[@]}" "${b[@]}"
do printf "%s%*s|\n" "$i" "$[15 - $(wc -L <<< "$i")]" ""
done
0 |
00 |
000 |
0000 |
00000 |
0 |
├─00 |
├─000 |
├─0000 |
└─00000 |
wc -L
打印输入的显示宽度,因此它也适用于双宽字符等
答案3
我做了一些网络搜索,但我无法在纯 Bash 中找到你的问题的解决方案,我认为可能没有。我发现了以下 StackOverflow 帖子:
我还在 Unix StackExchange 上发现了以下帖子:
这接受的解决方案其中包括以下解释:
POSIX需要
printf
来%-20s
计算这 20 个字节不是人物尽管这和printf
打印没什么意义文本,格式化(参见讨论在奥斯汀集团(POSIX)和bash
邮件列表)。
看来您想做的事情可能无法实现,printf
并且您必须推出自己的解决方案。
我能够使用 Python 脚本生成所需的输出。也许你会发现它很有用:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""script.py"""
# Set the default character encoding to UTF-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
# Array of ASCII characters
a=[("0")]
a+=[("00")]
a+=[("000")]
a+=[("0000")]
a+=[("00000")]
# Array of UTF-8 Characters
b=[("0")]
b+=[("├─00")]
b+=[("├─000")]
b+=[("├─0000")]
b+=[("└─00000")]
# Print the elements from both arrays
for x in a + b:
print (u"%-15s|" % x).encode('utf-8')
这是我运行脚本时得到的结果:
user@host:~$ python script.py
0 |
00 |
000 |
0000 |
00000 |
0 |
├─00 |
├─000 |
├─0000 |
└─00000 |
答案4
为什么 printf 会“缩小”变音符号?有一些适当的解决方案,通过调用适当的工具来做到这一点,因为bash
内部缺少该功能或通过切换到不同的 shell,但如果您真的想bash
仅使用内置命令来实现它,那么有一些方法可以实现单宽度(可能是多宽度)字节)字符。
$string
在 bash 中,就像在所有 POSIX shell 中一样,您可以使用 来获取 a 的字符宽度${#string}
,${#string}
但在 C 语言环境中可以获取以字节为单位的宽度。
因此,您可以用以下方法来解释差异:
clength() { clength=${#1}; }
blength() { local LC_ALL=C; blength=${#1}; }
align() {
local format="$1" width="$2" arg blength clength
shift 2
for arg do
clength "$arg"; blength "$arg"
printf "$format" "$((width + blength - clength))" "$arg"
done
}
a=(0 00 000 0000 00000)
b=(0 ├─00 ├─000 ├─0000 └─00000)
align '%-*s|\n' 12 "${a[@]}" "${b[@]}"
为了解决零宽度(如组合标记)或双宽度字符,除非bash
您准备好在脚本中对此类字符的列表进行硬编码(或使用终端转义序列告诉终端对齐文本(最后一个例子那里, 或者那里)并对所有支持的终端硬编码转义序列,因为bash
也没有 terminfo/termcap 的内置接口)。据我所知,zsh 和 ksh93 是唯一内置支持对齐可变显示宽度字符的 shell(示例也在链接的问答)。