该TAB
字符是控制字符,当发送到终端¹时,使终端的光标移至下一个制表位。默认情况下,在大多数终端中,制表位之间相隔8列,但这是可配置的。
您还可以不定期地设置制表位:
$ tabs 3 9 11; printf '\tx\ty\tz\n'
x y z
只有终端知道TAB将在右侧移动多少列。
您可以通过在发送选项卡之前和之后从终端查询光标位置来获取该信息。
如果要针对给定的行手动进行计算,并假设该行打印在屏幕的第一列,则需要:
- 知道制表位在哪里²
- 知道每个字符的显示宽度
- 知道屏幕的宽度
- 决定是否要处理其他控制字符,例如
\r
(将光标移到第一列)还是\b
将光标移回...)
如果您假定制表符停靠点是每8列,该行适合屏幕,并且没有其他控制字符或终端无法正常显示的字符(或非字符),则可以简化此操作。
使用GNU时wc
,如果该行存储在$line
:
width=$(printf %s "$line" | wc -L)
width_without_tabs=$(printf %s "$line" | tr -d '\t' | wc -L)
width_of_tabs=$((width - width_without_tabs))
wc -L
给出输入中最宽线的宽度。它通过wcwidth(3)
确定字符的宽度并假定制表位是每8列来做到这一点。
对于具有相同假设的非GNU系统,请参见@Kusalananda的方法。更好的做法是,它允许您指定制表符停止点,但是不幸的是expand
,当输入包含多字节字符或0宽度(如组合字符)或全角字符时,当前(至少)当前不适用于GNU 。
¹请注意,尽管这样做stty tab3
,tty设备行规程将接管制表符处理(根据发送光标到终端之前基于游标可能在何处的想法将TAB转换为空格),并实现制表符每8列停止一次。在Linux上进行测试,它似乎可以正确处理CR,LF和BS字符以及多字节UTF-8字符(iutf8
也已提供),但仅此而已。它假定所有其他非控制字符(包括零宽度,双角字符)的宽度为1,(显然)不处理转义序列,不能正确包装...这可能是针对那些无法执行制表符处理。
无论如何,tty行规程确实需要知道光标在哪里并使用上面的启发式方法,因为在使用icanon
行编辑器时(例如,为诸如此类的应用程序输入文本时cat
,无需实现自己的行编辑器),按TabBackspace,行规需要知道要发送多少个BS字符以擦除该Tab字符以进行显示。如果您更改了制表符停止的位置(与相似tabs 12
),则会注意到制表符没有正确擦除。如果在按之前输入全角字符,则相同TabBackspace。
²为此,您可以发送制表符并在每个字符之后查询光标位置。就像是:
tabs=$(
saved_settings=$(stty -g)
stty -icanon min 1 time 0 -echo
gawk -vRS=R -F';' -vORS= < /dev/tty '
function out(s) {print s > "/dev/tty"; fflush("/dev/tty")}
BEGIN{out("\r\t\33[6n")}
$NF <= prev {out("\r"); exit}
{print sep ($NF - 1); sep=","; prev = $NF; out("\t\33[6n")}'
stty "$saved_settings"
)
然后,您可以将其用作expand -t "$tabs"
@Kusalananda的解决方案。
x
在调用expand
否则之前用其他一个全角字符(例如)替换空格,同时还要计算最初在输入中的空格。