确定制表符 '\t' 在一行中的长度

确定制表符 '\t' 在一行中的长度

在文本处理字段中,有没有办法知道制表符的长度是 8 个字符(默认长度)还是更少?

例如,如果我有一个带有制表符分隔符的示例文件,并且字段内容少于一个制表符 (≤7),并且如果之后有一个制表符,则该制表符将仅为“制表符大小 – 字段大小” ' 的长度。

有没有办法获得一行中制表符的总长度?我不是在寻找选项卡的数量(即 10 个选项卡不应返回 10),而是在寻找这些选项卡的字符长度。

对于以下输入数据(字段之间用制表符分隔,并且只有一个制表符):

field0  field00 field000        last-field
fld1    fld11   fld001  last-fld
fd2     fld3    last-fld

我希望计算每行中制表符的长度,所以

11
9
9

答案1

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 。


1 但请注意,如果您这样做stty tab3,tty 设备行规则将接管制表符处理(根据其自己对发送到终端之前光标可能所在位置的想法将制表符转换为空格)并每 8 列实现制表符停止。在 Linux 上测试,它似乎可以正确处理 CR、LF 和 BS 字符以及多字节 UTF-8 字符(前提是iutf8也打开),但仅此而已。它假设所有其他非控制字符(包括零宽度、双宽度字符)的宽度为 1,它(显然)不处理转义序列,不能正确换行...这可能适用于以下终端:无法进行制表符处理。

在任何情况下, tty 行规则确实需要知道光标在哪里并使用上面的启发式方法,因为当使用icanon行编辑器时(例如,当您为没有实现自己的行编辑器的应用程序输入文本时cat),当您按TabBackspace,线路纪律需要知道要发送多少个 BS 字符擦除用于显示的制表符。如果您更改制表符停止位置(如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 的解决方案一样使用它。

答案2

$ expand file | awk '{ print gsub(/ /, " ") }'
11
9
9

POSIXexpand实用程序将制表符扩展为空格。该awk脚本计算并输出替换每行上所有空格所需的替换次数。

为了避免计算输入文件中任何预先存在的空格:

$ tr ' ' '@' <file | expand | awk '{ print gsub(/ /, " ") }'

其中@是保证不存在于输入数据中的字符。

如果您希望每个制表符有 10 个空格而不是普通的 8 个:

$ tr ' ' '@' <file | expand -t 10 | awk '{ print gsub(/ /, " ") }'
9 
15
13

答案3

perl

perl -F/\\t/ -lpe '$c = 0; $F[-1] eq "" or pop @F; $_ = (map { $c += 8 - (length) % 8 } @F)[-1]' file

或者:

perl -MList::Util=reduce -lpe \
    '@F = split /\t/, $_, -1; pop @F if $F[-1] ne ""; $_ = reduce { $a + $b } map { 8 - (length) % 8 } @F' file

如果您希望制表符具有不同的长度,您可以将上面的 8 更改为其他值。

答案4

也使用expand,但通过 bash 参数操作来计算空格数:

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11

相关内容