Jaki byłby najbliższy przenośny sposób uzyskania szerokości wyświetlania (co najmniej na terminalu (takim, który wyświetla znaki w bieżących ustawieniach regionalnych o właściwej szerokości)) ciągu znaków ze skryptu powłoki.
Interesuje mnie przede wszystkim szerokość znaków niekontrolowanych, ale mile widziane są również rozwiązania uwzględniające znaki sterujące, takie jak backspace, powrót karetki, tabulacja pozioma.
Innymi słowy, szukam API powłoki wokół wcswidth()
funkcji POSIX.
To polecenie powinno zwrócić:
$ that-command 'unix' # 4 fullwidth characters
8
$ that-command 'Stéphane' # 9 characters, one of which zero-width
8
$ that-command 'もで 諤奯ゞ' # 5 double-width Japanese characters and a space
11
Można użyć ksh93
„s printf '%<n>Ls'
, która bierze pod uwagę szerokość znaków dla dopełnienia do <n>
kolumn, lub col
polecenie (z na przykład printf '++%s\b\b--\n' <character> | col -b
), aby spróbować i czerpać że istnieje Text :: CharWidth perl
moduł co najmniej, ale są tam bardziej bezpośrednie lub przenośne podejścia.
Jest to mniej więcej kontynuacja tego drugiego pytania, które dotyczyło wyświetlania tekstu po prawej stronie ekranu, dla którego musisz mieć te informacje przed wyświetleniem tekstu.
źródło
Odpowiedzi:
W emulatorze terminali można użyć raportu pozycji kursora, aby uzyskać pozycje przed / po, np. Z
i sprawdź, jak szerokie są znaki drukowane na terminalu. Ponieważ jest to sekwencja kontrolna ECMA-48 (a także VT100) obsługiwana przez prawie każdy terminal, którego prawdopodobnie będziesz używać, jest dość przenośny.
Na przykład
Ostatecznie emulator terminala określa szerokość drukowania, z powodu następujących czynników:
wcswidth
sam nie mówi, jak traktowane są łączące postacie; POSIX nie wspomina o tym aspekcie w opisie tej funkcji.wcswidth
samej (patrz na przykład Rozdział 2. Konfiguracja Cygwin ).xterm
na przykład ma możliwość wyboru znaków o podwójnej szerokości dla potrzebnych konfiguracji.Połączenia API powłoki
wcswidth
są obsługiwane w różnym stopniu:Są one mniej lub bardziej bezpośrednie: symulacja
wcswidth
w przypadku Perla, wywołanie środowiska wykonawczego C z Ruby i Pythona. Możesz nawet użyć przekleństw, np. Z Pythona (który obsługiwałby łączenie znaków):filter
funkcji (dla pojedynczych linii)addstr
, sprawdzając, czy nie ma błędu (w przypadku, gdy jest za długi), a następnie określ pozycję końcowąendwin
(które nie powinno zrobićrefresh
)Użycie przekleństw do wyjścia (zamiast dostarczania informacji z powrotem do skryptu lub bezpośredniego wywoływania
tput
) wyczyści całą linię (filter
ogranicza ją do linii).źródło
wcswidth()
ma do powiedzenia o czymkolwiek.plink
,TERM=xterm
mimo że nie reaguje ona na żadną sekwencję kontrolną. Ale nie używam bardzo egzotycznych terminali.fold
najwyraźniej jest przeznaczony do obsługi znaków wielobajtowych i znaków o rozszerzonej szerokości . Oto jak powinien obsługiwać backspace: bieżąca liczba szerokości linii zostanie zmniejszona o jeden, chociaż liczba nigdy nie będzie ujemna. Narzędzie fold nie powinno wstawiać <nowej linii> bezpośrednio przed żadnym znakiem <backspace lub po nim, chyba że następujący znak ma szerokość większą niż 1 i spowodowałoby, że szerokość linii przekroczyłaby szerokość. możefold -w[num]
ipr +[num]
jakoś może zostać w jakiś sposób połączony?W przypadku łańcuchów jednowierszowych implementacja GNU
wc
ma opcję-L
(aka--max-line-length
), która robi dokładnie to, czego szukasz (z wyjątkiem znaków kontrolnych).źródło
tab
(zakłada tabulatory co 8 kolumn).wc -L <<< 'unix'
→ 8,wc -L <<< 'Stéphane'
→ 8 iwc -L <<< 'もで 諤奯ゞ'
→ 11. PS Uważasz, że „Stéphane” to dziewięć znaków, z których jeden ma szerokość zero? Wygląda mi na osiem znaków, z których jeden jest wielobajtowy.W moim
.profile
przypadku wywołuję skrypt, aby określić szerokość ciągu na terminalu. Używam tego, logując się na konsoli komputera, na którym nie mam zaufania do zestawu systemowegoLC_CTYPE
, lub gdy loguję się zdalnie i nie mogę zaufać,LC_CTYPE
aby dopasować się do strony zdalnej. Mój skrypt wysyła zapytanie do terminala, zamiast wywoływać jakąkolwiek bibliotekę, ponieważ o to właśnie chodziło w moim przypadku użycia: określ kodowanie terminala.Jest to kruche na kilka sposobów:
plink
, a ja rozwiązałem go za pomocą tejplinkx
metody ).To może, ale nie musi, pasować do twojego przypadku użycia.
Skrypt zwraca szerokość w stanie zwrotu przyciętą do 100. Przykładowe użycie:
źródło
printf "\r%*s\r" $((${#text}+8)) " ";
na końcucleanup
(dodanie 8 jest arbitralne; musi być wystarczająco długie, aby pokryć szersze wyjście starszych lokalizacji, ale wystarczająco wąskie, aby uniknąć zawijania linii). To sprawia, że test jest niewidoczny, choć zakłada również, że nic nie zostało wydrukowane na linii (co jest w porządku w a~/.profile
)text="Éé"
a następnie${#text}
podasz szerokość wyświetlania (dostaję się4
w terminalu innym niż Unicode i2
terminalu zgodnym z Unicode). Nie dotyczy to bash.${#text}
nie podaje szerokości wyświetlania. Podaje liczbę znaków w kodowaniu używanych przez bieżące ustawienia regionalne. Co jest dla mnie bezużyteczne, ponieważ chcę ustalić kodowanie terminala. Jest to przydatne, jeśli chcesz wyświetlić szerokość z innego powodu, ale nie jest ona dokładna, ponieważ nie każda postać ma szerokość jednej jednostki. Na przykład łączenie akcentów ma szerokość 0, a chińskie ideogramy mają szerokość 2.Eric Pruitt napisał imponującą implementację
wcwidth()
iw programiewcswidth()
Awk dostępną na wcwidth.awk . Zapewnia głównie 4 funkcjegdzie
wcscolumns()
toleruje również znaki niedrukowalne.Otworzyłem problem z pytaniem o obsługę TAB, ponieważ
wcscolumns($'My sign is\t鼠鼠')
powinien być większy niż 14. Aktualizacja: Eric dodał funkcjęwcsexpand()
rozszerzania TAB do spacji:źródło
Aby rozwinąć wskazówki dotyczące możliwych rozwiązań za pomocą
col
iwksh93
moim pytaniu:Używając
col
zbsdmainutils
na Debianie (może nie działać z innymicol
implementacjami), aby uzyskać szerokość pojedynczego niekontrolowanego znaku:Przykład:
Rozszerzony na ciąg:
Korzystanie
ksh93
zprintf '%Ls'
:Korzystanie
perl
zText::CharWidth
:źródło