Bestimmen Sie, wie lange die Registerkarten '\ t' in einer Zeile sind

10

Gibt es in einem Textverarbeitungsfeld eine Möglichkeit, festzustellen, ob eine Registerkarte 8 Zeichen (die Standardlänge) oder weniger enthält?

Wenn ich beispielsweise eine Beispieldatei mit Tabulatortrennzeichen habe und der Inhalt eines Felds in weniger als eine Registerkarte passt (≤7), und wenn ich danach eine Registerkarte habe, ist diese Registerkarte nur 'Registerkartengröße - Feldgröße 'in der Länge.

Gibt es eine Möglichkeit, die Gesamtlänge der Registerkarten in einer Zeile abzurufen? Ich suche nicht nach der Anzahl der Registerkarten (dh 10 Registerkarten sollten nicht 10 zurückgeben), sondern nach der Zeichenlänge dieser Registerkarten.

Für die folgenden Eingabedaten (zwischen Feldern und nur einer Registerkarte begrenzte Registerkarte):

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

Ich erwarte, die Länge der Registerkarten in jeder Zeile zu zählen

11
9
9
αғsнιη
quelle

Antworten:

22

Das TABZeichen ist ein Steuerzeichen, das beim Senden an ein Terminal¹ den Cursor des Terminals zum nächsten Tabulator bewegt. Standardmäßig sind die Tabulatoren in den meisten Terminals 8 Spalten voneinander entfernt, dies ist jedoch konfigurierbar.

Sie können auch in unregelmäßigen Abständen Tabulatoren setzen:

$ tabs 3 9 11; printf '\tx\ty\tz\n'
  x     y z

Nur das Terminal weiß, wie viele Spalten rechts ein TAB den Cursor bewegt.

Sie können diese Informationen erhalten, indem Sie die Cursorposition vor und nach dem Senden der Registerkarte vom Terminal abfragen.

Wenn Sie diese Berechnung für eine bestimmte Zeile von Hand durchführen möchten und davon ausgehen, dass diese Zeile in der ersten Spalte des Bildschirms gedruckt wird, müssen Sie:

  • wissen, wo die Tabulatoren sind²
  • kennen die Anzeigebreite jedes Zeichens
  • kennen die Breite des Bildschirms
  • Entscheiden Sie, ob Sie andere Steuerzeichen wie \r(wodurch der Cursor in die erste Spalte \bbewegt wird ) oder den Cursor zurück bewegen möchten ...).

Dies kann vereinfacht werden, wenn Sie davon ausgehen, dass die Tabulatoren alle 8 Spalten angezeigt werden, die Zeile in den Bildschirm passt und keine anderen Steuerzeichen oder Zeichen (oder Nichtzeichen) vorhanden sind, die Ihr Terminal nicht richtig anzeigen kann.

Mit GNU wc, wenn die Zeile gespeichert ist in $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 -Lgibt die Breite der breitesten Linie in der Eingabe an. Dazu wird wcwidth(3)die Breite der Zeichen bestimmt und angenommen, dass die Tabulatoren alle 8 Spalten sind.

Für Nicht-GNU-Systeme und mit denselben Annahmen siehe @ Kusalanandas Ansatz . Es ist sogar noch besser, da Sie damit die Tabulatoren angeben können, aber derzeit leider nicht mit GNU funktionieren expand(zumindest), wenn die Eingabe Mehrbytezeichen oder Zeichen mit einer Breite von 0 (wie das Kombinieren von Zeichen) oder Zeichen mit doppelter Breite enthält.


¹ Beachten Sie jedoch stty tab3, dass in diesem Fall die tty-Geräteleitendisziplin die Tabulatorverarbeitung übernimmt (TAB in Leerzeichen konvertieren, basierend auf der eigenen Vorstellung, wo sich der Cursor möglicherweise befindet, bevor er an das Terminal gesendet wird) und Tabulatorstopps alle 8 Spalten implementiert. Beim Testen unter Linux scheint es richtig mit CR-, LF- und BS-Zeichen sowie Multibyte-UTF-8-Zeichen umzugehen (vorausgesetzt, es iutf8ist auch aktiviert ), aber das war es auch schon. Es wird davon ausgegangen, dass alle anderen Nicht-Steuerzeichen (einschließlich Zeichen mit der Breite Null und der doppelten Breite) eine Breite von 1 haben. Es verarbeitet (offensichtlich) keine Escape-Sequenzen und wird nicht richtig umbrochen. Dies ist wahrscheinlich für Terminals gedacht, die Tab-Verarbeitung kann nicht durchgeführt werden.

In jedem Fall muss die tty-Zeilendisziplin wissen, wo sich der Cursor befindet, und die oben genannten Heuristiken verwenden, da bei Verwendung des icanonZeileneditors (z. B. bei der Eingabe von Text für solche Anwendungen catkein eigener Zeileneditor implementiert wird) Drücken Sie TabBackspace, die Zeilendisziplin muss wissen, wie viele BS-Zeichen gesendet werden müssen, um das Tabulatorzeichen für die Anzeige zu löschen . Wenn Sie ändern, wo sich die Tabulatoren befinden (wie bei tabs 12), werden Sie feststellen, dass Tabulatoren nicht richtig gelöscht werden. Gleiches gilt, wenn Sie vor dem Drücken Zeichen mit doppelter Breite eingeben TabBackspace.


² Dazu können Sie Tabulatorzeichen senden und nach jedem die Cursorposition abfragen. Etwas wie:

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"
)

Dann können Sie dies als expand -t "$tabs"die Lösung von @ Kusalananda verwenden.

Stéphane Chazelas
quelle
7
$ expand file | awk '{ print gsub(/ /, " ") }'
11
9
9

Das POSIX- expandDienstprogramm erweitert Registerkarten zu Leerzeichen. Das awkSkript zählt und gibt die Anzahl der Ersetzungen aus, die erforderlich sind, um alle Leerzeichen in jeder Zeile zu ersetzen.

So vermeiden Sie das Zählen bereits vorhandener Leerzeichen in der Eingabedatei:

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

Dabei @handelt es sich um ein Zeichen, das in den Eingabedaten garantiert nicht vorhanden ist.

Wenn Sie 10 Leerzeichen pro Tab anstelle der normalen 8 möchten:

$ tr ' ' '@' <file | expand -t 10 | awk '{ print gsub(/ /, " ") }'
9 
15
13
Kusalananda
quelle
3
Sie möchten Leerzeichen durch ein anderes Zeichen mit einer Breite (wie x) ersetzen, bevor Sie es aufrufen. expandAndernfalls würden Sie auch die Leerzeichen zählen, die ursprünglich in der Eingabe enthalten waren.
Stéphane Chazelas
1
expandEs wird auch davon ausgegangen, dass alle 8 Spalten Tabulatoren gesetzt werden (obwohl Sie dies mit Optionen ändern können). Beachten Sie, dass die GNU-Implementierung keine Mehrbyte-Zeichen unterstützt (geschweige denn Zeichen mit 0 oder doppelter Breite). IIRC das FreeBSD ist OK.
Stéphane Chazelas
@ StéphaneChazelas Es sei denn natürlich, das ist Teil des Plans, die Breite der 0x09s mit den 0x20s zu zählen ;-)
can-ned_food
2

Mit perl:

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

Alternative:

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

Sie können 8 oben mit einem anderen Wert ändern, wenn TABs eine andere Länge haben sollen.

Satō Katsura
quelle
2

Auch mit expand, aber mit Bash-Parameter-Manipulation, um die Anzahl der Leerzeichen zu zählen:

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