กำหนดระยะเวลาที่แท็บ '\ 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 ความกว้าง (เช่นการรวมอักขระ) หรืออักขระความกว้างสองเท่า


¹โปรดทราบว่าถ้าคุณทำเช่นstty tab3นั้นวินัยของบรรทัดอุปกรณ์ tty จะเข้าควบคุมกระบวนการแท็บ (แปลง TAB เป็นช่องว่างตามแนวคิดของที่เคอร์เซอร์อาจอยู่ก่อนส่งไปยังเทอร์มินัล) และใช้แท็บหยุดทุก 8 คอลัมน์ การทดสอบบน Linux ดูเหมือนว่าจะจัดการกับอักขระ CR, LF และ BS อย่างถูกต้องรวมถึงตัวเลือก UTF-8 แบบมัลติไบต์ (ที่มีให้iutf8เช่นกัน) แต่ก็เกี่ยวกับมัน มันถือว่าตัวละครที่ไม่ใช่การควบคุมอื่น ๆ (รวมถึงตัวอักษรความกว้างเป็นศูนย์, ความกว้างสองเท่า) มีความกว้าง 1 มัน (ชัด) ไม่ได้จัดการลำดับหนีไม่ห่ออย่างถูกต้อง ... นั่นอาจจะมีไว้สำหรับอาคารที่ ไม่สามารถทำการประมวลผลแท็บได้

ไม่ว่าในกรณีใดวินัยของ tty line ไม่จำเป็นต้องรู้ว่าเคอร์เซอร์อยู่ที่ไหนและใช้ฮิวริสติกด้านบนเพราะเมื่อใช้ตัวicanonแก้ไขบรรทัด (เช่นเมื่อคุณป้อนข้อความสำหรับแอปพลิเคชันเช่นcatนั้น กดTabBackspace, วินัยของสายจำเป็นต้องรู้จำนวนอักขระ BS ที่จะส่งเพื่อลบอักขระแท็บนั้นเพื่อแสดง หากคุณเปลี่ยนตำแหน่งที่แท็บหยุด (เช่นเดียวกับtabs 12) คุณจะสังเกตเห็นว่าแท็บไม่ถูกลบอย่างถูกต้อง เดียวกันถ้าคุณป้อนตัวอักษรแบบ double-width 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มิฉะนั้นคุณจะนับช่องว่างที่อยู่ในอินพุตเช่นกัน
Stéphane Chazelas

1
expandยังถือว่าแท็บหยุดทุก ๆ 8 คอลัมน์ (แม้ว่าคุณสามารถเปลี่ยนได้ด้วยตัวเลือก) โปรดทราบว่าการใช้งาน GNU ไม่รองรับอักขระแบบหลายไบต์ (ยกเว้นความกว้าง 0 หรือความกว้างสองเท่า) IIRC FreeBSD ตัวหนึ่งก็โอเค
Stéphane Chazelas

@ 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 ข้างต้นด้วยค่าอื่น ๆ ได้หากคุณต้องการให้แท็บมีความยาวต่างกัน


2

ยังใช้expandแต่มีการจัดการพารามิเตอร์ bash เพื่อนับจำนวนช่องว่าง:

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.