Tentukan berapa lama tab '\ t' ada di satu baris


10

Dalam bidang pemrosesan teks, adakah cara untuk mengetahui apakah tab memiliki panjang 8 karakter (panjang default) atau kurang?

Misalnya, jika saya memiliki file sampel dengan pembatas tab dan konten bidang yang cocok dalam kurang dari satu tab (≤7), dan jika saya memiliki tab setelah itu, maka tab itu hanya akan menjadi ukuran tab - ukuran tab - ukuran bidang 'panjangnya

Apakah ada cara untuk mendapatkan total panjang tab pada satu baris? Saya tidak mencari jumlah tab (yaitu 10 tab seharusnya tidak mengembalikan 10) tetapi karakter panjangnya.

Untuk data input berikut (tab dibatasi antara bidang dan hanya satu tab):

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

Saya berharap untuk menghitung panjang tab di setiap baris, jadi

11
9
9

Jawaban:


22

The TABkarakter adalah karakter kontrol yang ketika dikirim ke terminal¹ yang membuat terminal kursor pindah ke tab-stop berikutnya. Secara default, di sebagian besar terminal, tab berhenti terpisah 8 kolom, tetapi itu dapat dikonfigurasi.

Anda juga dapat memiliki penghentian tab pada interval tidak teratur:

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

Hanya terminal yang tahu berapa kolom di sebelah kanan TAB yang akan memindahkan kursor.

Anda dapat memperoleh informasi itu dengan menanyakan posisi kursor dari terminal sebelum dan setelah tab dikirim.

Jika Anda ingin membuat perhitungan dengan tangan untuk garis yang diberikan dan dengan asumsi garis itu dicetak pada kolom pertama layar, Anda harus:

  • tahu di mana tab-stops adalah²
  • tahu lebar tampilan setiap karakter
  • tahu lebar layar
  • putuskan apakah Anda ingin menangani karakter kontrol lain seperti \r(yang memindahkan kursor ke kolom pertama) atau \byang mengembalikan kursor ...)

Ini dapat disederhanakan jika Anda mengasumsikan tab berhenti setiap 8 kolom, garis pas di layar dan tidak ada karakter atau karakter kontrol lain (atau non-karakter) yang terminal Anda tidak dapat ditampilkan dengan benar.

Dengan GNU wc, jika saluran disimpan di $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 -Lmemberikan lebar garis terluas di inputnya. Itu dilakukan dengan menggunakan wcwidth(3)untuk menentukan lebar karakter dan dengan asumsi tab berhenti setiap 8 kolom.

Untuk sistem non-GNU, dan dengan asumsi yang sama, lihat pendekatan @ Kusalananda . Ini bahkan lebih baik karena memungkinkan Anda menentukan berhenti tab tetapi sayangnya saat ini tidak bekerja dengan GNU expand(setidaknya) ketika input berisi karakter multi-byte atau 0-lebar (seperti menggabungkan karakter) atau karakter lebar ganda.


¹ catat bahwa jika Anda melakukannya stty tab3, disiplin garis perangkat tty akan mengambil alih pemrosesan tab (konversikan TAB ke spasi berdasarkan gagasannya sendiri tentang di mana kursor mungkin sebelum dikirim ke terminal) dan implementasikan tab berhenti setiap 8 kolom. Pengujian di Linux, tampaknya menangani dengan benar karakter CR, LF dan BS serta multibyte UTF-8 (disediakan iutf8juga aktif) tetapi hanya itu saja. Ini mengasumsikan semua karakter non-kontrol lainnya (termasuk nol-lebar, karakter lebar ganda) memiliki lebar 1, itu (jelas) tidak menangani urutan pelarian, tidak membungkus dengan benar ... Itu mungkin dimaksudkan untuk terminal yang tidak dapat melakukan pemrosesan tab.

Bagaimanapun, disiplin garis tty memang perlu tahu di mana kursor berada dan menggunakan heuristik di atas, karena ketika menggunakan icanoneditor baris (seperti ketika Anda memasukkan teks untuk aplikasi seperti catitu tidak menerapkan editor baris mereka sendiri), ketika Anda tekan TabBackspace, garis disiplin perlu tahu berapa banyak karakter BS untuk mengirim untuk menghapus karakter Tab untuk tampilan. Jika Anda mengubah tempat tab berhenti (seperti dengan tabs 12), Anda akan melihat bahwa Tab tidak terhapus dengan benar. Sama jika Anda memasukkan karakter lebar ganda sebelum menekan TabBackspace.


² Untuk itu, Anda dapat mengirim karakter tab dan menanyakan posisi kursor setelah masing-masing. Sesuatu seperti:

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

Kemudian, Anda dapat menggunakannya dengan expand -t "$tabs"menggunakan solusi @ Kusalananda.


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

expandUtilitas POSIX memperluas tab ke spasi. The awkjumlah naskah dan output jumlah pergantian pemain yang dibutuhkan untuk mengganti semua spasi pada setiap baris.

Untuk menghindari penghitungan ruang yang sudah ada sebelumnya dalam file input:

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

di mana @karakter yang dijamin tidak ada dalam input data.

Jika Anda ingin 10 spasi per tab, bukan 8 yang biasa:

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

3
Anda ingin mengganti spasi dengan karakter satu-lebar lainnya (seperti x) sebelum memanggil expandsebaliknya, Anda juga akan menghitung spasi yang awalnya di input juga.
Stéphane Chazelas

1
expandjuga mengasumsikan tab-stops setiap 8 kolom (meskipun Anda dapat mengubahnya dengan opsi). Perhatikan bahwa implementasi GNU tidak mendukung karakter multi-byte (apalagi 0-lebar atau ganda-lebar). IIRC yang FreeBSD tidak apa-apa.
Stéphane Chazelas

@ StéphaneChazelas Kecuali, tentu saja, itu adalah bagian dari rencana untuk menghitung lebar 0x09 dengan 0x20 ;-)
can-ned_food

2

Dengan perl:

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

Kalau tidak:

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

Anda dapat mengubah 8 di atas dengan nilai lain jika Anda ingin TAB memiliki panjang yang berbeda.


2

Juga menggunakan expand, tetapi dengan manipulasi parameter bash untuk menghitung jumlah spasi:

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.