'\ T' sekmelerinin bir satırda ne kadar uzun olduğunu belirleyin


10

Bir metin işleme alanında, bir sekmenin 8 karakter uzunluğunda (varsayılan uzunluk) veya daha az olduğunu bilmenin bir yolu var mı?

Örneğin, sekme ayırıcılı bir örnek dosyam varsa ve bir alanın içeriği birden fazla sekmeye sığıyorsa (≤7) ve bundan sonra bir sekmem varsa, bu sekme yalnızca 'sekme boyutu - alan boyutu olur ' uzunluğunda.

Bir satırdaki toplam sekme uzunluğunu almanın bir yolu var mı? Sekme sayısı (yani 10 sekme 10 döndürmemelidir) ama bu sekmelerin karakter uzunluğu aramıyorum.

Aşağıdaki giriş verileri için (alanlar ve yalnızca bir sekme arasında ayrılmış sekme):

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

Her satırdaki sekmelerin uzunluğunu saymayı umuyorum, bu yüzden

11
9
9

Yanıtlar:


22

TABKarakteri bir terminal¹ gönderilen bir sonraki sekme durağına terminalin imleci hamle yapar bir kontrol karakteridir. Varsayılan olarak, çoğu terminalde sekme durakları 8 sütun aralıklıdır, ancak bu yapılandırılabilir.

Ayrıca, düzensiz aralıklarla sekme durakları da olabilir:

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

Yalnızca terminal, bir SEKME'nin sağdaki kaç sütunu imleci hareket ettireceğini bilir.

Sekme gönderilmeden önce ve sonra terminalden imleç konumunu sorgulayarak bu bilgileri alabilirsiniz.

Belirli bir satır için bu hesaplamayı el ile yapmak ve bu satırın ekranın ilk sütununa yazdırıldığını varsayarsanız, şunları yapmanız gerekir:

  • sekme duraklarının nerede olduğunu bilir²
  • her karakterin ekran genişliğini bilir
  • ekranın genişliğini bilir
  • \r(imleci ilk sütuna \btaşır ) veya imleci geri taşır ... gibi diğer kontrol karakterlerini işlemek isteyip istemediğinize karar verin.

Sekme duraklarının her 8 sütunda olduğunu, satırın ekrana sığdığını ve terminalinizin düzgün bir şekilde görüntüleyemeyeceği başka kontrol karakterleri veya karakterleri (veya karakter olmayan) olmadığını varsayarsanız basitleştirilebilir.

GNU ile wc, eğer hat şurada depolanmışsa $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 -Lgirişindeki en geniş çizginin genişliğini verir. Bunu wcwidth(3)karakterlerin genişliğini belirlemek için ve sekme duraklarının her 8 sütunda olduğunu varsayarak yapar.

GNU olmayan sistemler ve aynı varsayımlarla, @ Kusalananda'nın yaklaşımına bakınız . Sekme duraklarını belirtmenize izin verdiği için daha da iyidir, ancak expandgiriş çok baytlı karakterler veya 0 genişlikli (karakterleri birleştirmek gibi) veya çift genişlikli karakterler içerdiğinde maalesef şu anda GNU (en azından) ile çalışmaz .


Ancak bunu yaparsanız stty tab3, tty cihaz hattı disiplinin sekme işlemeyi devralacağını (TAB'ı imlecin terminale göndermeden önce nerede olabileceğine dair kendi fikrine dayanarak boşluklara dönüştürecek) ve sekmenin her 8 sütunda durduğunu unutmayın. Linux'ta test yaparken, CR, LF ve BS karakterlerinin yanı sıra çok baytlı UTF-8 karakterlerini (sağlandığı da iutf8açıktır) düzgün ele alıyor gibi görünüyor, ancak hepsi bu. Diğer tüm kontrol dışı karakterlerin (sıfır genişlik, çift genişlikli karakterler dahil) 1 genişliğine sahip olduğunu varsayar (açıkçası) kaçış dizilerini işlemez, düzgün bir şekilde sarılmaz ... Bu muhtemelen terminaller için tasarlanmıştır. sekme işleme yapamıyor.

Her durumda, tty satır disiplininin imlecin nerede olduğunu bilmesi gerekir ve yukarıdaki buluşsal yöntemleri kullanır, çünkü icanonsatır düzenleyiciyi kullanırken (bunun gibi uygulamalar için metin girdiğinizde catkendi satır düzenleyicilerini uygulamadığınızda olduğu gibi ), basın TabBackspace, çizgi disiplin göndermek için kaç BS karakter bilmesi gereken silme gösterilecek yeri Sekme karakteri. Sekmenin durduğu yeri değiştirirseniz (ile olduğu gibi tabs 12), Sekmelerin düzgün bir şekilde silinmediğini fark edersiniz. Tuşuna basmadan önce çift genişlikli karakterler girerseniz aynı olur TabBackspace.


² Bunun için sekme karakterleri gönderebilir ve her birinin ardından imleç konumunu sorgulayabilirsiniz. Gibi bir şey:

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

Daha sonra bunu expand -t "$tabs"@ Kusalananda'nın çözümünü kullanarak kullanabilirsiniz .


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

POSIX expandyardımcı programı sekmeleri boşluklara genişletir. awkKomut sayımları ve çıkışları her bir satırda tüm boşlukları değiştirmek için gerekli ikamelerin sayısı.

Giriş dosyasında önceden var olan boşlukların sayılmasını önlemek için:

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

burada @giriş verilerinde bulunmadığı garanti edilen bir karakter vardır.

Sıradan 8 yerine sekme başına 10 boşluk istiyorsanız:

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

3
Boşlukları xaramadan önce boşlukları başka bir tek genişlikli karakterle (gibi ) değiştirmek istersiniz expand, başlangıçta girişte bulunan boşlukları da saymış olursunuz.
Stéphane Chazelas

1
expandayrıca her 8 sütunda sekme durağı olduğu varsayılır (bunu seçeneklerle değiştirebilirsiniz). GNU uygulamasının çok baytlı karakterleri desteklemediğini unutmayın (yalnız 0 genişlik veya çift genişlik olanlar). IIRC FreeBSD bir sorun yok.
Stéphane Chazelas

@ StéphaneChazelas Tabii ki, bu 0x209 genişliğini 0x20s ;-) ile sayma planının bir parçası olmadığı sürece
can-ned_food

2

İle perl:

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

Alternatif:

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

Sekmelerin farklı bir uzunluğa sahip olmasını istiyorsanız, yukarıdaki 8 değerini başka bir değerle değiştirebilirsiniz.


2

Ayrıca expand, boşluk sayısını saymak için bash parametresi manipülasyonu ile:

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.