Визначте, як довгі вкладки '\ t' знаходяться у рядку


10

Чи є в полі для обробки тексту спосіб знати, чи вкладка має 8 символів (довжина за замовчуванням) чи менше?

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

Чи є спосіб отримати загальну довжину вкладок на рядку? Я не шукаю кількість вкладок (тобто 10 вкладок не має повертати 10), а довжину символів цих вкладок.

Для таких вхідних даних (вкладка з розділеними полями та лише одна вкладка):

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

Я очікую підрахунок довжини вкладок у кожному рядку, так

11
9
9

Відповіді:


22

TABСимвол являє собою керуючий символ , який при відправці на terminal¹ робить рух курсора терміналу до наступної закладки зупинки. За замовчуванням у більшості терміналів зупинки вкладок розташовані на 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, та з тими ж припущеннями, див. Підхід @ Кусалаланда . Це ще краще, оскільки дозволяє вказати зупинки вкладок, але, на жаль, наразі не працює з GNU expand(принаймні), коли вхід містить багатобайтові символи або символи 0-ширини (як поєднання символів) або подвійну ширину символів.


Though зауважте, що якщо ви це зробите stty tab3, дисципліна лінії tty пристрою візьме на себе обробку вкладок (перетворіть TAB в пробіли, виходячи з власного уявлення про те, де міг би знаходитися курсор перед відправленням в термінал), і впроваджувати вкладку зупинятиме кожні 8 стовпців. Тестуючи на Linux, схоже, що він обробляє належним чином символи CR, LF та BS, а також багатобайтові символи UTF-8 (надається iutf8також увімкнено), але це стосується цього. Він передбачає, що всі інші символи без керування (включаючи нульову ширину, подвійну ширину символів) мають ширину 1, він (очевидно) не обробляє послідовності втечі, не обертається належним чином ... Це, мабуть, призначене для терміналів, які не вдається обробити вкладку.

У будь-якому випадку, дисципліна tty line повинна знати, де знаходиться курсор, і використовує ті евристики, наведені вище, тому що при використанні 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.


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 Якщо, звичайно, це не є частиною плану підрахунку ширини 0x09s з 0x20s ;-)
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 вище на якесь інше значення, якщо ви хочете, щоб таблички TAB мали іншу довжину.


2

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

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.