Определите, как долго вкладки '\ t' находятся в строке

10

В поле обработки текста есть способ узнать, если длина вкладки составляет 8 символов (длина по умолчанию) или меньше?

Например, если у меня есть образец файла с разделителем вкладок и содержимое поля помещается менее чем на одну вкладку (≤7), и если после этого у меня есть вкладка, то эта вкладка будет иметь только размер вкладки - размер поля ' в длину.

Есть ли способ получить общую длину вкладок в строке? Я не ищу количество вкладок (т.е. 10 вкладок не должно возвращать 10), а длину символов этих вкладок.

Для следующих входных данных (табуляция разделена между полями и только одной вкладкой):

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

Я рассчитываю посчитать длину вкладок в каждой строке, поэтому

11
9
9
αғsнιη
источник

Ответы:

22

Символ 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 . Это даже лучше, так как позволяет задавать позиции табуляции, но, к сожалению, в настоящее время не работает с GNU expand(по крайней мере), когда ввод содержит многобайтовые символы или символы 0 ширины (например, символы объединения) или символы двойной ширины.


¹ обратите внимание, что, если вы это сделаете stty tab3, дисциплина строки устройства tty возьмет на себя обработку вкладок (преобразует TAB в пробелы на основе своего собственного представления о том, где может находиться курсор перед отправкой в ​​терминал), и реализация табуляции останавливается через каждые 8 ​​столбцов. При тестировании в Linux, он, кажется, правильно обрабатывает символы CR, LF и BS, а также многобайтовые символы UTF-8 ( iutf8также включены), но это все. Предполагается, что все другие неконтролирующие символы (включая символы нулевой ширины и двойной ширины) имеют ширину 1, он (очевидно) не обрабатывает escape-последовательности, не переносится должным образом ... Это, вероятно, предназначено для терминалов, которые не могу сделать обработку вкладок.

В любом случае дисциплина линий 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.

Стефан Шазелас
источник
7
$ expand file | awk '{ print gsub(/ /, " ") }'
11
9
9

expandУтилита POSIX расширяет вкладки до пробелов. На awkсчете сценария и выводит количество замен , необходимых для замены всех пробелов в каждой строке.

Чтобы избежать подсчета любых ранее существующих пробелов во входном файле:

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

где @символ, который гарантированно не существует во входных данных.

Если вы хотите 10 пробелов на вкладку вместо обычных 8:

$ tr ' ' '@' <file | expand -t 10 | awk '{ print gsub(/ /, " ") }'
9 
15
13
Кусалананда
источник
3
Вы хотели бы заменить пробелы каким-либо другим символом одной ширины (например x) перед вызовом, в expandпротивном случае вы бы также посчитали пробелы, которые изначально были во входных данных.
Стефан
1
expandтакже предполагает табуляцию через каждые 8 ​​столбцов (хотя вы можете изменить это с помощью опций). Обратите внимание, что реализация GNU не поддерживает многобайтовые символы (не говоря уже о 0 или двойной ширине). IIRC во FreeBSD один в порядке.
Стефан
@ StéphaneChazelas Если, конечно, это не является частью плана по подсчету ширины 0x09 с 0x20 с ;-)
can-ned_food
2

С 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 выше с другим значением, если хотите, чтобы вкладки имели другую длину.

Сату Кацура
источник
2

Также используется expand, но с манипулированием параметрами bash для подсчета количества пробелов:

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11
Гленн Джекман
источник