Xác định khoảng thời gian các tab '\ t' trên một dòng


10

Trong một trường xử lý văn bản có cách nào để biết một tab có độ dài 8 ký tự (độ dài mặc định) hay ít hơn không?

Ví dụ: nếu tôi có tệp mẫu có dấu phân cách tab và nội dung của trường phù hợp với ít hơn một tab (≤7) và nếu sau đó tôi có một tab, thì tab đó sẽ chỉ có kích thước tab - kích thước trường 'chiều dài.

Có cách nào để có được tổng chiều dài của các tab trên một dòng không? Tôi không tìm kiếm số lượng tab (tức là 10 tab không nên trả về 10) mà là độ dài ký tự của các tab đó.

Đối với dữ liệu đầu vào sau (tab được phân tách giữa các trường và chỉ một tab):

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

Tôi hy vọng sẽ đếm chiều dài của các tab trong mỗi dòng, vì vậy

11
9
9

Câu trả lời:


22

TABtự này là một ký tự điều khiển mà khi được gửi đến một thiết bị đầu cuối¹ làm cho con trỏ của thiết bị đầu cuối di chuyển đến điểm dừng tab tiếp theo. Theo mặc định, trong hầu hết các thiết bị đầu cuối, các điểm dừng tab cách nhau 8 cột, nhưng đó là cấu hình.

Bạn cũng có thể có các điểm dừng tab trong khoảng thời gian không đều:

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

Chỉ có thiết bị đầu cuối biết có bao nhiêu cột bên phải một TAB sẽ di chuyển con trỏ.

Bạn có thể lấy thông tin đó bằng cách truy vấn vị trí con trỏ từ thiết bị đầu cuối trước và sau khi tab được gửi.

Nếu bạn muốn thực hiện phép tính đó bằng tay cho một dòng nhất định và giả sử rằng dòng đó được in ở cột đầu tiên của màn hình, bạn sẽ cần:

  • biết nơi dừng của tab²
  • biết chiều rộng hiển thị của mỗi ký tự
  • biết chiều rộng của màn hình
  • quyết định xem bạn muốn xử lý các ký tự điều khiển khác như \r(di chuyển con trỏ đến cột đầu tiên) hay \bđiều đó di chuyển con trỏ trở lại ...)

Nó có thể được đơn giản hóa nếu bạn giả sử các điểm dừng của tab là cứ sau 8 cột, dòng vừa với màn hình và không có ký tự điều khiển hoặc ký tự nào (hoặc không phải ký tự) mà thiết bị đầu cuối của bạn không thể hiển thị đúng.

Với GNU wc, nếu dòng được lưu trữ trong $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 -Lcho chiều rộng của đường rộng nhất trong đầu vào của nó. Nó thực hiện điều đó bằng cách sử dụng wcwidth(3)để xác định độ rộng của các ký tự và giả sử các điểm dừng của tab là mỗi 8 cột.

Đối với các hệ thống không phải GNU và với các giả định tương tự, hãy xem phương pháp của @ Kusalananda . Thậm chí còn tốt hơn khi nó cho phép bạn chỉ định các điểm dừng tab nhưng tiếc là hiện tại không hoạt động với GNU expand(ít nhất) khi đầu vào chứa các ký tự nhiều byte hoặc 0 chiều rộng (như kết hợp các ký tự) hoặc các ký tự có độ rộng gấp đôi.


Lưu ý rằng nếu bạn làm như vậy stty tab3, kỷ luật dòng thiết bị tty sẽ đảm nhận việc xử lý tab (chuyển đổi TAB thành khoảng trắng dựa trên ý tưởng của chính nó về nơi con trỏ có thể ở trước khi gửi đến thiết bị đầu cuối) và tab thực hiện dừng mỗi 8 cột. Thử nghiệm trên Linux, nó dường như xử lý đúng các ký tự CR, LF và BS cũng như các UTF-8 đa nhân (cung cấp iutf8cũng được bật) nhưng đó là về nó. Nó giả sử tất cả các ký tự không điều khiển khác (bao gồm các ký tự có độ rộng bằng không, độ rộng gấp đôi) có chiều rộng là 1, rõ ràng là nó không xử lý các chuỗi thoát, không bao bọc đúng cách ... Điều đó có lẽ dành cho các thiết bị đầu cuối không thể xử lý tab.

Trong mọi trường hợp, kỷ luật dòng tty không cần biết con trỏ ở đâu và sử dụng các heuristic ở trên, bởi vì khi sử dụng trình chỉnh sửa icanondòng (như khi bạn nhập văn bản cho các ứng dụng như catthế không thực hiện trình chỉnh sửa dòng riêng của chúng), khi bạn nhấn TabBackspace, kỷ luật dòng cần biết có bao nhiêu ký tự BS để gửi để xóa ký tự Tab đó để hiển thị. Nếu bạn thay đổi nơi dừng của tab (như với tabs 12), bạn sẽ nhận thấy rằng Tab không bị xóa đúng cách. Tương tự nếu bạn nhập các ký tự có chiều rộng gấp đôi trước khi nhấn TabBackspace.


² Vì thế, bạn có thể gửi các ký tự tab và truy vấn vị trí con trỏ sau mỗi ký tự. Cái gì đó như:

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

Sau đó, bạn có thể sử dụng expand -t "$tabs"giải pháp đó như sử dụng giải pháp của @ Kusalananda.


7
$ expand file | awk '{ print gsub(/ /, " ") }'
11
9
9

expandTiện ích POSIX mở rộng các tab thành không gian. Các awktội kịch bản và kết quả đầu ra số lượng thay thế cần thiết để thay thế tất cả các không gian trên mỗi dòng.

Để tránh đếm bất kỳ khoảng trống có sẵn nào trong tệp đầu vào:

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

trong đó @một ký tự được đảm bảo không tồn tại trong dữ liệu đầu vào.

Nếu bạn muốn 10 khoảng trống trên mỗi tab thay vì 8 thông thường:

$ tr ' ' '@' <file | expand -t 10 | awk '{ print gsub(/ /, " ") }'
9 
15
13

3
Bạn muốn thay thế khoảng trắng bằng một số ký tự một chiều khác (như x) trước khi gọi expandkhác, bạn cũng sẽ đếm các khoảng trắng ban đầu trong đầu vào.
Stéphane Chazelas

1
expandcũng giả sử các điểm dừng tab cứ sau 8 cột (mặc dù bạn có thể thay đổi điều đó bằng các tùy chọn). Lưu ý rằng việc triển khai GNU không hỗ trợ các ký tự nhiều byte (huống chi là các ký tự 0 chiều rộng hoặc gấp đôi chiều rộng). IIRC một FreeBSD là OK.
Stéphane Chazelas

@ StéphaneChazelas Tất nhiên trừ khi đó là một phần của kế hoạch tính chiều rộng của 0x09s với 0x20s ;-)
can-ned_food

2

Với perl:

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

Cách khác:

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

Bạn có thể thay đổi 8 ở trên với một số giá trị khác nếu bạn muốn TAB có độ dài khác nhau.


2

Cũng sử dụng expand, nhưng với thao tác tham số bash để đếm số lượng khoảng trắng:

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.