Określ, jak długo tabulatory „\ t” są w linii

10

Czy w polu przetwarzania tekstu można sprawdzić, czy tabulator ma długość 8 znaków (długość domyślna) lub mniej?

Na przykład, jeśli mam przykładowy plik z separatorem tabulatorów, a zawartość pola mieści się w mniej niż jednej tabulatorze (≤7), a jeśli mam tabulację, to będzie to tylko „rozmiar tabulatora - rozmiar pola” ' na długość.

Czy istnieje sposób na uzyskanie całkowitej długości zakładek w linii? Nie szukam liczby kart (tj. 10 kart nie powinno zwracać 10), ale długości znaków tych kart.

Dla następujących danych wejściowych (tabulator rozdzielony między polami i tylko jedna tabulator):

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

Spodziewam się policzyć długość zakładek w każdej linii, więc

11
9
9
αғsнιη
źródło

Odpowiedzi:

22

TABZnak jest znak kontrolny, który gdy wysłany do terminal¹ powoduje ruch kursora terminala do następnej karty-stop. Domyślnie w większości terminali tabulatory są oddalone od siebie o 8 kolumn, ale można je konfigurować.

Możesz także ustawić tabulatory w nieregularnych odstępach czasu:

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

Tylko terminal wie, ile kolumn po prawej stronie TAB poruszy kursor.

Możesz uzyskać te informacje, sprawdzając pozycję kursora z terminala przed i po wysłaniu zakładki.

Jeśli chcesz wykonać te obliczenia ręcznie dla danej linii i zakładając, że linia jest drukowana w pierwszej kolumnie ekranu, musisz:

  • wiedzieć, gdzie są tabulatory²
  • znać szerokość wyświetlania każdego znaku
  • znać szerokość ekranu
  • zdecyduj, czy chcesz obsługiwać inne znaki sterujące, takie jak \r(który przesuwa kursor do pierwszej kolumny), czy \bteż przesuwa kursor do tyłu ...)

Można to uprościć, zakładając, że tabulatory są co 8 kolumn, linia mieści się na ekranie i nie ma żadnych innych znaków sterujących ani znaków (lub znaków innych niż), których terminal nie może poprawnie wyświetlić.

W GNU wc, jeśli wiersz jest przechowywany w $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 -Lpodaje szerokość najszerszej linii na wejściu. Robi to za pomocą wcwidth(3)określania szerokości znaków i zakładając, że tabulatory są co 8 kolumn.

W przypadku systemów innych niż GNU i przy takich samych założeniach, patrz podejście @ Kusalanandy . Jest to nawet lepsze, ponieważ pozwala określić tabulatory, ale niestety obecnie nie działa z GNU expand(przynajmniej), gdy wejście zawiera znaki wielobajtowe lub szerokość 0 (jak łączenie znaków) lub znaki o podwójnej szerokości.


¹ zauważ jednak, że jeśli to zrobisz stty tab3, dyscyplina linii urządzenia tty przejmie przetwarzanie tabulatorów (konwertuje TAB na spacje w oparciu o własne wyobrażenie o tym, gdzie może być kursor przed wysłaniem do terminala) i implementuje tabulatory co 8 kolumn. Testując na Linuksie, wydaje się, że poprawnie obsługuje znaki CR, LF i BS, a także znaki wielobajtowe UTF-8 (pod warunkiem, że iutf8jest włączony), ale o to chodzi. Zakłada, że ​​wszystkie inne znaki niekontrolowane (w tym znaki o zerowej szerokości i podwójnej szerokości) mają szerokość 1, to (oczywiście) nie obsługuje sekwencji specjalnych, nie jest poprawnie zawijane ... To prawdopodobnie jest przeznaczone dla terminali, które nie można przetwarzać kart.

W każdym razie dyscyplina linii tty musi wiedzieć, gdzie jest kursor, i korzysta z powyższych heurystyk, ponieważ podczas korzystania z icanonedytora linii (np. Podczas wprowadzania tekstu dla aplikacji takich jak catten nie implementuje własnego edytora linii), gdy naciśnij TabBackspace, dyscyplina linii musi wiedzieć, ile znaków BS należy wysłać, aby usunąć ten znak Tab do wyświetlenia. Jeśli zmienisz miejsce zatrzymania tabulatorów (podobnie jak w przypadku tabs 12), zauważysz, że Tabs nie zostały poprawnie usunięte. To samo, jeśli przed naciśnięciem wprowadzisz znaki o podwójnej szerokości TabBackspace.


² W tym celu można wysyłać znaki tabulacji i sprawdzać pozycję kursora po każdym z nich. Coś jak:

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

Następnie możesz użyć tego jako expand -t "$tabs"rozwiązania @ Kusalananda.

Stéphane Chazelas
źródło
7
$ expand file | awk '{ print gsub(/ /, " ") }'
11
9
9

POSIX'owych expandnarzędzie rozszerza spacje. Do awkliczy skryptów i wysyła liczbę podstawień potrzebnych do zastąpienia wszystkich miejsc na każdej linii.

Aby uniknąć zliczania wcześniejszych spacji w pliku wejściowym:

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

gdzie @jest gwarantowanym znakiem, że nie istnieje w danych wejściowych.

Jeśli chcesz 10 spacji na kartę zamiast zwykłych 8:

$ tr ' ' '@' <file | expand -t 10 | awk '{ print gsub(/ /, " ") }'
9 
15
13
Kusalananda
źródło
3
xPrzed wywołaniem chcesz zamienić spacje na inny znak o jednej szerokości (np. ) expand, Policzysz również spacje, które początkowo były w danych wejściowych.
Stéphane Chazelas
1
expandzakłada także tabulatory co 8 kolumn (choć można to zmienić za pomocą opcji). Zauważ, że implementacja GNU nie obsługuje znaków wielobajtowych (nie mówiąc już o znakach o szerokości 0 lub podwójnej szerokości). IIRC wersja FreeBSD jest OK.
Stéphane Chazelas
@ StéphaneChazelas O ile, oczywiście, jest to część planu liczenia szerokości 0x09 w 0x20 ;-)
can-ned_food
2

Z perl:

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

Alternatywnie:

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

Możesz zmienić 8 powyżej z inną wartością, jeśli chcesz, aby tabele miały inną długość.

Satō Katsura
źródło
2

Również przy użyciu expand, ale z manipulacją parametrami bash do zliczania spacji:

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