รับความกว้างการแสดงผลของสตริงอักขระ


15

สิ่งที่ใกล้เคียงที่สุดกับวิธีพกพาเพื่อให้ได้ความกว้างในการแสดงผล (บนเทอร์มินัลอย่างน้อยหนึ่งตัวที่แสดงอักขระในโลแคลปัจจุบันที่มีความกว้างที่ถูกต้อง) ของสตริงอักขระจากสคริปต์เชลล์

ฉันสนใจในความกว้างของตัวละครที่ไม่ได้ควบคุมเป็นหลัก แต่โซลูชั่นที่คำนึงถึงตัวควบคุมบัญชีเช่น backspace, carriage return, tabulation ในแนวนอนก็ยินดีเช่นกัน

กล่าวอีกนัยหนึ่งฉันกำลังมองหาshell API รอบwcswidth()ฟังก์ชัน POSIX

คำสั่งนั้นควรกลับมา:

$ 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

หนึ่งสามารถใช้ksh93's printf '%<n>Ls'ที่คำนึงถึงความกว้างตัวอักษรสำหรับการขยายไปยัง<n>คอลัมน์หรือcolคำสั่ง (มีตัวอย่างprintf '++%s\b\b--\n' <character> | col -b) จะลองและได้รับมานั้นมีข้อความ :: CharWidth perlโมดูลอย่างน้อย แต่จะมีวิธีการโดยตรงมากขึ้นหรือแบบพกพา

นั่นคือการติดตามคำถามอื่น ๆที่เพิ่มเติมเกี่ยวกับการแสดงข้อความทางด้านขวาของหน้าจอซึ่งคุณจะต้องมีข้อมูลนั้นก่อนที่จะแสดงข้อความ


คำตอบ:


7

ในเทอร์มินัลอีมูเลเตอร์เราสามารถใช้รายงานตำแหน่งเคอร์เซอร์เพื่อรับตำแหน่งก่อน / หลังเช่นจาก

...record position
printf '%s' $string
...record position

และค้นหาความกว้างของอักขระที่พิมพ์บนเทอร์มินัล เนื่องจากเป็นลำดับการควบคุม ECMA-48 (รวมถึง VT100) ที่สนับสนุนโดยเครื่องปลายทางเกือบทุกเครื่องที่คุณใช้

สำหรับการอ้างอิง

    CSI Ps n Device Status Report (DSR)
              ...
                Ps = 6 -> รายงานตำแหน่งเคอร์เซอร์ (CPR) [แถว; คอลัมน์]
              ผลลัพธ์คือ CSI r; c R

ในที่สุดเทอร์มินัลอีมูเลเตอร์จะกำหนดความกว้างที่พิมพ์ได้เนื่องจากปัจจัยเหล่านี้:

  • การตั้งค่าโลแคลส่งผลต่อวิธีการจัดรูปแบบสตริง แต่ชุดของไบต์ที่ส่งไปยังเทอร์มินัลจะตีความตามวิธีการกำหนดค่าเทอร์มินัล (สังเกตว่าบางคนจะโต้แย้งว่ามันต้องเป็น UTF-8 ในขณะที่ความสะดวกในการพกพาคือคุณสมบัติที่ร้องขอในคำถาม)
  • wcswidthเพียงอย่างเดียวไม่ได้บอกวิธีจัดการอักขระรวม POSIX ไม่ได้กล่าวถึงแง่มุมนี้ในคำอธิบายของฟังก์ชันนั้น
  • ตัวละครบางตัว (สายการวาดภาพเป็นต้น) ซึ่งหนึ่งอาจจะใช้สำหรับการรับเป็นคนเดียวที่มีความกว้าง (ตาม Unicode) "width คลุมเครือ" บั่นทอนพกพาของแอพลิเคชันที่ใช้wcswidthอยู่คนเดียว (ดูตัวอย่างบทที่ 2 การตั้งค่า Cygwin ) xtermเช่นมีข้อกำหนดสำหรับการเลือกอักขระความกว้างสองเท่าสำหรับการกำหนดค่าที่จำเป็นนี้
  • เพื่อจัดการกับสิ่งอื่นนอกเหนือจากตัวอักษรที่พิมพ์ได้คุณจะต้องพึ่งพาเทอร์มินัลอีมูเลเตอร์ (เว้นแต่ว่าคุณต้องการจำลองมัน)

การเรียก Shell API wcswidthได้รับการสนับสนุนในระดับที่แตกต่าง:

สิ่งเหล่านี้โดยตรงหรือน้อยกว่า: จำลองwcswidthในกรณีของ Perl เรียกใช้ C runtime จาก Ruby และ Python คุณสามารถใช้คำสาปเช่นจาก Python (ซึ่งจะจัดการกับการรวมอักขระ):

  • เริ่มต้นเทอร์มินัลโดยใช้setupterm (ไม่มีข้อความถูกเขียนลงบนหน้าจอ)
  • ใช้filterฟังก์ชั่น (สำหรับบรรทัดเดียว)
  • วาดข้อความที่จุดเริ่มต้นของบรรทัดด้วยaddstrตรวจสอบข้อผิดพลาด (ในกรณีที่ยาวเกินไป) จากนั้นสำหรับตำแหน่งสิ้นสุด
  • หากมีห้องให้ปรับตำแหน่งเริ่มต้น
  • โทรendwin(ซึ่งไม่ควรทำrefresh)
  • เขียนข้อมูลผลลัพธ์เกี่ยวกับตำแหน่งเริ่มต้นไปยังเอาต์พุตมาตรฐาน

การใช้ curses สำหรับเอาต์พุต (แทนที่จะป้อนข้อมูลกลับไปที่สคริปต์หรือการโทรโดยตรงtput) จะเป็นการล้างทั้งบรรทัด ( filterไม่ จำกัด เฉพาะบรรทัด)


ฉันคิดว่านี่ต้องเป็นวิธีเดียวจริงๆ ถ้าเทอร์มินัลไม่รองรับตัวอักษรสองตัวความกว้างก็ไม่สำคัญว่าwcswidth()จะพูดอะไรเกี่ยวกับอะไรเลย
mikeserv

ในทางปฏิบัติปัญหาเดียวที่ฉันมีกับวิธีนี้คือplinkซึ่งตั้งค่าTERM=xtermแม้ว่ามันจะไม่ตอบสนองต่อลำดับการควบคุมใด ๆ แต่ฉันไม่ได้ใช้ขั้วที่แปลกใหม่มาก
Gilles 'หยุดความชั่วร้าย'

ขอบคุณ แต่แนวคิดก็คือการรับข้อมูลนั้นก่อนที่จะแสดงสตริงบนเทอร์มินัล (เพื่อทราบตำแหน่งที่จะแสดงมันคือการติดตามคำถามล่าสุดเกี่ยวกับการแสดงสตริงที่ด้านขวาของเทอร์มินัลบางทีฉันควรจะพูดถึงว่า แม้ว่าคำถามจริงของฉันเกี่ยวกับการเข้าถึง wcswidth จากเชลล์) @mikeserv, ใช่ wcswidth () อาจผิดเกี่ยวกับวิธีการที่เทอร์มินัลเฉพาะจะแสดงสตริงเฉพาะ แต่ที่ใกล้ที่สุดเท่าที่คุณสามารถไปที่ทางออกเทอร์มินัลที่เป็นอิสระและนั่นคือสิ่งที่ col / ksh-printf ใช้ในระบบของฉัน
Stéphane Chazelas

ฉันรู้เรื่องนี้แล้ว แต่ wcswidth ไม่สามารถเข้าถึงได้โดยตรงยกเว้นผ่านคุณสมบัติที่ไม่สามารถพกพาได้ (คุณสามารถทำสิ่งนี้ได้ในภาษา Perl โดยตั้งสมมติฐานบางอย่าง - ดูที่search.cpan.org/dist/Text-CharWidth/CharWidth.pm ) . คำถามการจัดตำแหน่งขวาโดยวิธีอาจจะปรับปรุง (บางที) โดยการเขียนสตริงไปทางซ้ายล่างแล้วใช้ตำแหน่งเคอร์เซอร์และตัวควบคุมการแทรกเพื่อเลื่อนไปทางขวาล่าง
Thomas Dickey

1
StéphaneChazelas @ - foldเป็น spec'd เห็นได้ชัดในการจัดการหลายไบต์และขยายความกว้างของตัวละคร นี่คือวิธีที่จะจัดการ backspace: จำนวนปัจจุบันของความกว้างของบรรทัดจะลดลงหนึ่งแม้ว่าจำนวนจะไม่เป็นลบ ยูทิลิตี้การพับจะไม่แทรก <newline> ทันทีก่อนหรือหลัง <backspace> ใด ๆ เว้นแต่ว่าอักขระต่อไปนี้มีความกว้างมากกว่า 1และจะทำให้ความกว้างของบรรทัดเกินความกว้าง อาจfold -w[num]และpr +[num]อาจจะร่วมกันอย่างใด?
mikeserv

5

สำหรับสตริงหนึ่งบรรทัดการใช้งาน GNU ของwcมีตัวเลือก-L(aka --max-line-length) ที่ทำสิ่งที่คุณกำลังมองหา (ยกเว้นตัวควบคุมการควบคุม)


1
ขอบคุณ ฉันไม่รู้ว่ามันจะกลับมาที่ความกว้างของหน้าจอ โปรดทราบว่าการใช้งาน FreeBSD นั้นมีตัวเลือก -L ด้วยเช่นกัน doc บอกว่ามันจะคืนค่าจำนวนตัวอักษรในบรรทัดที่ยาวที่สุด แต่การทดสอบของฉันดูเหมือนจะระบุว่ามันเป็นจำนวนไบต์แทน (ไม่ใช่ความกว้างของการแสดงผลใน anycase) OS / X ไม่มี -L แม้ว่าฉันคาดว่ามันจะได้มาจาก FreeBSD
Stéphane Chazelas

ดูเหมือนว่าจะจัดการtabเช่นกัน (ถือว่าแท็บหยุดทุก ๆ 8 คอลัมน์)
Stéphane Chazelas

ที่จริงสำหรับสตริงมากขึ้นกว่าหนึ่งบรรทัดผมจะบอกว่ามันก็ยังไม่ตรงกับสิ่งที่ฉันกำลังมองหาในขณะที่จะจัดการกับตัวควบคุม LF อย่างถูกต้อง
Stéphane Chazelas

@ StéphaneChazelas: คุณยังมีปัญหาว่าจะส่งกลับจำนวนไบต์มากกว่าจำนวนอักขระหรือไม่ ฉันทดสอบข้อมูลของคุณและรับผลลัพธ์ที่คุณต้องการ: wc -L <<< 'unix'→ 8,  wc -L <<< 'Stéphane'→ 8, และ  wc -L <<< 'もで 諤奯ゞ'→ 11. ป.ล. คุณคิดว่า“ Stéphane” เป็นอักขระเก้าตัวซึ่งหนึ่งในนั้นคือความกว้างเป็นศูนย์? ดูเหมือนว่าฉันจะมีตัวละครแปดตัวหนึ่งในนั้นคือหลายไบต์
G-Man กล่าวว่า 'Reinstate Monica'

@ G-Man ฉันอ้างถึงการใช้งาน FreeBSD ซึ่งใน FreeBSD 12.0 และที่ตั้ง UTF-8 ยังคงดูเหมือนว่าจะนับไบต์ โปรดทราบว่าéสามารถเขียนได้โดยใช้ตัวอักษร U + 00E9 หนึ่งตัวหรือตัวอักษร U + 0065 (e) ตามด้วย U + 0301 (การรวมการเน้นเสียงแบบเฉียบพลัน) ซึ่งตัวหลังเป็นตัวอักษรที่แสดงในคำถาม
Stéphane Chazelas

4

ในของ.profileฉันฉันเรียกสคริปต์เพื่อกำหนดความกว้างของสายอักขระบนเทอร์มินัล ฉันใช้สิ่งนี้เมื่อลงชื่อเข้าใช้บนคอนโซลของเครื่องที่ฉันไม่เชื่อถือชุดระบบLC_CTYPEหรือเมื่อฉันลงชื่อเข้าใช้จากระยะไกลและไม่สามารถเชื่อถือได้LC_CTYPEเพื่อให้ตรงกับด้านระยะไกล สคริปต์ของฉันค้นหาเทอร์มินัลแทนที่จะเรียกใช้ไลบรารีใด ๆ เพราะนั่นคือจุดรวมในกรณีการใช้งานของฉัน: พิจารณาการเข้ารหัสของเทอร์มินัล

นี่เปราะบางได้หลายวิธี:

  • มันปรับเปลี่ยนการแสดงผลดังนั้นมันจึงไม่ใช่ประสบการณ์การใช้งานที่ดีนัก
  • มีสภาพการแข่งขันหากโปรแกรมอื่นแสดงบางอย่างผิดปกติ
  • มันล็อคถ้าเทอร์มินัลไม่ตอบสนอง (ไม่กี่ปีที่ผ่านมาฉันถามวิธีการปรับปรุงนี้แต่มันก็ไม่ได้เป็นปัญหามากนักในทางปฏิบัติดังนั้นฉันไม่เคยได้รับรอบเพื่อเปลี่ยนวิธีการแก้ปัญหากรณีเดียวที่ฉันพบ terminal ที่ไม่ตอบสนองคือ Windows Emacs ที่เข้าถึงไฟล์ระยะไกลจากเครื่อง Linux ด้วยplinkวิธีการและฉันแก้ไขโดยใช้plinkxวิธีแทน )

นี่อาจจะตรงกับกรณีการใช้งานของคุณหรือไม่

#! /bin/sh

if [ z"$ZSH_VERSION" = z ]; then :; else
  emulate sh 2>/dev/null
fi
set -e

help_and_exit () {
  cat <<EOF
Usage: $0 {-NUMBER|TEXT}
Find out the width of TEXT on the terminal.

LIMITATION: this program has been designed to work in an xterm. Only
xterm and sufficiently compatible terminals will work. If you think
this program may be blocked waiting for input from the the terminal,
try entering the characters "0n0n" (digit 0, lowercase letter n,
repeat).

Display TEXT and erase it. Find out the position of the cursor before
and after displaying TEXT so as to compute the width of TEXT. The width
is returned as the exit code of the program. A value of 100 is returned if
the text is wider than 100 columns.

TEXT may contain backslash-escapes: \\0DDD represents the byte whose numeric
value is DDD in octal. Use '\\\\' to include a single backslash character.

You may use -NUMBER instead of TEXT (if TEXT begins with a dash, use
"-- TEXT"). This selects one of the built-in texts that are designed
to discriminate between common encodings. The following table lists
supported values of NUMBER (leftmost column) and the widths of the
sample text in several encodings.

  1  ASCII=0 UTF-8=2 latinN=3 8bits=4
EOF
  exit
}

builtin_text () {
  case $1 in
    -*[!0-9]*)
      echo 1>&2 "$0: bad number: $1"
      exit 119;;
    -1) # UTF8: {\'E\'e}; latin1: {\~A\~A\copyright}; ASCII: {}
      text='\0303\0211\0303\0251';;
    *)
      echo 1>&2 "$0: there is no text number $1. Stop."
      exit 118;;
  esac
}

text=
if [ $# -eq 0 ]; then
  help_and_exit 1>&2
fi
case "$1" in
  --) shift;;
  -h|--help) help_and_exit;;
  -[0-9]) builtin_text "$1";;
  -*)
    echo 1>&2 "$0: unknown option: $1"
    exit 119
esac
if [ z"$text" = z ]; then
  text="$1"
fi

printf "" # test that it is there (abort on very old systems)

csi='\033['
dsr_cpr="${csi}6n" # Device Status Report --- Report Cursor Position
dsr_ok="${csi}5n" # Device Status Report --- Status Report

stty_save=`stty -g`
if [ z"$stty_save" = z ]; then
  echo 1>&2 "$0: \`stty -g' failed ($?)."
  exit 3
fi
initial_x=
final_x=
delta_x=

cleanup () {
  set +e
  # Restore terminal settings
  stty "$stty_save"
  # Restore cursor position (unless something unexpected happened)
  if [ z"$2" = z ]; then
    if [ z"$initial_report" = z ]; then :; else
      x=`expr "${initial_report}" : "\\(.*\\)0"`
      printf "%b" "${csi}${x}H"
    fi
  fi
  if [ z"$1" = z ]; then
    # cleanup was called explicitly, so don't exit.
    # We use `trap : 0' rather than `trap - 0' because the latter doesn't
    # work in older Bourne shells.
    trap : 0
    return
  fi
  exit $1
}
trap 'cleanup 120 no' 0
trap 'cleanup 129' 1
trap 'cleanup 130' 2
trap 'cleanup 131' 3
trap 'cleanup 143' 15

stty eol 0 eof n -echo
printf "%b" "$dsr_cpr$dsr_ok"
initial_report=`tr -dc \;0123456789`
# Get the initial cursor position. Time out if the terminal does not reply
# within 1 second. The trick of calling tr and sleep in a pipeline to put
# them in a process group, and using "kill 0" to kill the whole process
# group, was suggested by Stephane Gimenez at
# /unix/10698/timing-out-in-a-shell-script
#trap : 14
#set +e
#initial_report=`sh -c 'ps -t $(tty) -o pid,ppid,pgid,command >/tmp/p;
#                       { tr -dc \;0123456789 >&3; kill -14 0; } |
#                       { sleep 1; kill -14 0; }' 3>&1`
#set -e
#initial_report=`{ sleep 1; kill 0; } |
#                { tr -dc \;0123456789 </dev/tty; kill 0; }`
if [ z"$initial_report" = z"" ]; then
  # We couldn't read the initial cursor position, so abort.
  cleanup 120
fi
# Write some text and get the final cursor position.
printf "%b%b" "$text" "$dsr_cpr$dsr_ok"
final_report=`tr -dc \;0123456789`

initial_x=`expr "$initial_report" : "[0-9][0-9]*;\\([0-9][0-9]*\\)0" || test $? -eq 1`
final_x=`expr "$final_report" : "[0-9][0-9]*;\\([0-9][0-9]*\\)0" || test $? -eq 1`
delta_x=`expr "$final_x" - "$initial_x" || test $? -eq 1`

cleanup
# Zsh has function-local EXIT traps, even in sh emulation mode. This
# is a long-standing bug.
trap : 0

if [ $delta_x -gt 100 ]; then
  delta_x=100
fi
exit $delta_x

สคริปต์ส่งคืนความกว้างในสถานะย้อนกลับถูกตัดเป็น 100 ตัวอย่างการใช้งาน:

widthof -1
case $? in
  0) export LC_CTYPE=C;; # 7-bit charset
  2) locale_search .utf8 .UTF-8;; # utf8
  3) locale_search .iso88591 .ISO8859-1 .latin1 '';; # 8-bit with nonprintable 128-159, we assume latin1
  4) locale_search .iso88591 .ISO8859-1 .latin1 '';; # some full 8-bit charset, we assume latin1
  *) export LC_CTYPE=C;; # weird charset
esac

สิ่งนี้มีประโยชน์กับฉัน (แม้ว่าฉันจะใช้เวอร์ชันย่อของคุณเป็นส่วนใหญ่) ฉันใช้มันให้สวยขึ้นเล็กน้อยโดยการเพิ่มprintf "\r%*s\r" $((${#text}+8)) " ";ที่ส่วนท้ายของcleanup(การเพิ่ม 8 เป็นกฎเกณฑ์มันต้องยาวพอที่จะครอบคลุมผลลัพธ์ที่กว้างขึ้นของสถานที่เก่า แต่แคบพอที่จะหลีกเลี่ยงการพันบรรทัด) นี่ทำให้การทดสอบมองไม่เห็นแม้ว่าจะถือว่าไม่มีสิ่งใดพิมพ์บนบรรทัด (ซึ่งใช้ได้ใน~/.profile)
Adam Katz

ที่จริงแล้วมันปรากฏขึ้นจากการทดลองเล็ก ๆ น้อย ๆ ที่ใน zsh (5.7.1) คุณสามารถทำได้text="Éé"และจากนั้น${#text}จะให้ความกว้างของการแสดงผล (ฉันได้รับ4ในเทอร์มินัลที่ไม่ใช่ยูนิโค้ดและ2ในเทอร์มินัล สิ่งนี้ไม่เป็นความจริงสำหรับการทุบตี
Adam Katz

@ AdamKatz ${#text}ไม่ได้ให้ความกว้างในการแสดงผลแก่คุณ มันทำให้คุณจำนวนตัวละครในการเข้ารหัสที่ใช้โดยสถานที่ปัจจุบัน ซึ่งไม่มีประโยชน์สำหรับวัตถุประสงค์ของฉันเนื่องจากฉันต้องการตรวจสอบการเข้ารหัสของเทอร์มินัล มันจะมีประโยชน์ถ้าคุณต้องการความกว้างหน้าจอด้วยเหตุผลอื่น แต่มันไม่ถูกต้องเพราะไม่ใช่ตัวละครทุกตัวที่มีความกว้างหนึ่งหน่วย ตัวอย่างเช่นการรวมสำเนียงมีความกว้าง 0 และอักษรจีนมีความกว้าง 2
Gilles 'ดังนั้น - หยุดความชั่วร้าย'

ใช่เป็นจุดที่ดี มันอาจตอบสนองคำถามของStéphane แต่ไม่ใช่ความตั้งใจดั้งเดิมของคุณ (ซึ่งจริงๆแล้วเป็นสิ่งที่ฉันต้องการจะทำเช่นเดียวกันดังนั้นฉันจึงปรับรหัสของคุณ) หวังว่าความคิดเห็นแรกของฉันจะเป็นประโยชน์กับคุณ Gilles
Adam Katz

3

เอริคพรูอิทเขียนการดำเนินงานที่น่าประทับใจของwcwidth()และwcswidth()ใน Awk ใช้ได้ที่wcwidth.awk มันมี 4 ฟังก์ชั่นเป็นหลัก

wcscolumns(), wcstruncate(), wcwidth(), wcswidth()

ซึ่งwcscolumns()ยังทนต่ออักขระที่ไม่สามารถพิมพ์ได้

$ cat wcscolumns.awk 
{ printf "%d\n", wcscolumns($0) }
$ awk -f wcwidth.awk -f wcscolumns.awk <<< 'unix'
8
$ awk -f wcwidth.awk -f wcscolumns.awk <<< 'Stéphane'
8
$ awk -f wcwidth.awk -f wcscolumns.awk <<< 'もで 諤奯ゞ'
11
$ awk -f wcwidth.awk -f wcscolumns.awk <<< $'My sign is\t鼠鼠'
14

ฉันเปิดปัญหาที่ถามเกี่ยวกับการจัดการแท็บเนื่องจากwcscolumns($'My sign is\t鼠鼠')ควรมากกว่า 14 อัปเดต: Eric เพิ่มฟังก์ชันwcsexpand()เพื่อขยายแท็บไปยังช่องว่าง:

$ cat >wcsexpand.awk 
{ printf "%d\n", wcscolumns( wcsexpand($0, 8) ) }
$ awk -f wcwidth.awk -f wcsexpand.awk <<< $'My sign is\t鼠鼠'
20
$ echo $'鼠\tone\n鼠鼠\ttwo'
      one
鼠鼠    two
$ awk -f wcwidth.awk -f wcsexpand.awk <<< $'鼠\tone\n鼠鼠\ttwo'
11
11

1

หากต้องการขยายคำแนะนำในการแก้ปัญหาที่เป็นไปได้โดยใช้colและksh93ในคำถามของฉัน:

การใช้colจากbsdmainutilsบนบนเดเบียน (อาจใช้ไม่ได้กับcolการใช้งานอื่น ๆ) เพื่อรับความกว้างของอักขระที่ไม่ใช่ตัวควบคุมเดียว:

charwidth() {
  set "$(printf '...%s\b\b...\n' "$1" | col -b)"
  echo "$((${#1} - 4))"
}

ตัวอย่าง:

$ charwidth x
1
$ charwidth $'\u301'
0
$ charwidth $'\u94f6'
2

ขยายสำหรับสตริง:

stringwidth() {
   awk '
     BEGIN{
       s = ARGV[1]
       l = length(s)
       for (i=0; i<l; i++) {
         s1 = s1 ".."
         s2 = s2 "\b\b"
       }
       print s1 s s2 s1
       exit
     }' "$1" | col -b | awk '
        {print length - 2 * length(ARGV[2]); exit}' - "$1"
}

ใช้ksh93's printf '%Ls':

charwidth() {
  set "$(printf '.%2Ls.' "$1")"
  echo "$((5 - ${#1}))"
}

stringwidth() {
  set "$(printf '.%*Ls.' "$((2*${#1}))" "$1")" "$1"
  echo "$((2 + 3 * ${#2} - ${#1}))"
}

ใช้perl's Text::CharWidth:

stringwidth() {
  perl -MText::CharWidth=mbswidth -le 'print mbswidth shift' "$@"
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.