Determinar quanto tempo as guias '\ t' estão em uma linha

10

Em um campo de processamento de texto, há uma maneira de saber se uma guia possui 8 caracteres (o tamanho padrão) ou menos?

Por exemplo, se eu tiver um arquivo de amostra com delimitador de guias e o conteúdo de um campo caber em menos de uma guia (≤7), e se eu tiver uma guia depois disso, essa guia será apenas 'tamanho da guia - tamanho do campo ' em comprimento.

Existe uma maneira de obter o comprimento total de guias em uma linha? Não estou procurando o número de guias (ou seja, 10 guias não devem retornar 10), mas o tamanho dos caracteres dessas guias.

Para os seguintes dados de entrada (guia delimitado entre campos e apenas um guia):

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

Espero contar o comprimento das guias em cada linha, então

11
9
9
αғsнιη
fonte

Respostas:

22

O TABcaractere é um caractere de controle que, quando enviado para um terminal¹, faz com que o cursor do terminal se mova para a próxima tabulação. Por padrão, na maioria dos terminais, as paradas de tabulação têm 8 colunas de distância, mas isso é configurável.

Você também pode ter tabulações em intervalos irregulares:

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

Somente o terminal sabe quantas colunas à direita um TAB moverá o cursor.

Você pode obter essas informações consultando a posição do cursor no terminal antes e depois do envio da guia.

Se você deseja fazer esse cálculo manualmente para uma determinada linha e assumindo que a linha seja impressa na primeira coluna da tela, será necessário:

  • saber onde estão as paradas de tabulação²
  • conhecer a largura de exibição de cada caractere
  • conhecer a largura da tela
  • decida se deseja manipular outros caracteres de controle como \r(que move o cursor para a primeira coluna) ou \bque move o cursor para trás ...)

Isso pode ser simplificado se você assumir que as paradas de tabulação são a cada 8 colunas, a linha se encaixa na tela e não há outros caracteres de controle ou caracteres (ou não caracteres) que o seu terminal não pode exibir corretamente.

Com o GNU wc, se a linha estiver armazenada em $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 -Lfornece a largura da linha mais larga em sua entrada. Ele faz isso usando wcwidth(3)para determinar a largura dos caracteres e assumindo que as paradas de tabulação estejam a cada 8 colunas.

Para sistemas não-GNU e com as mesmas suposições, consulte a abordagem de @ Kusalananda . É ainda melhor, pois permite especificar as paradas da guia, mas infelizmente atualmente não funciona com o GNU expand(pelo menos) quando a entrada contém caracteres de vários bytes ou largura 0 (como combinar caracteres) ou caracteres de largura dupla.


Though observe que, se o fizer stty tab3, a disciplina da linha do dispositivo tty assumirá o processamento da guia (converter TAB em espaços com base em sua própria idéia de onde o cursor pode estar antes de enviar para o terminal) e a guia implementar implementará a cada 8 colunas. Testando no Linux, parece lidar adequadamente com caracteres CR, LF e BS, bem como caracteres UTF-8 de vários bytes ( iutf8também fornecido ), mas é isso. Ele assume que todos os outros caracteres que não são de controle (incluindo caracteres de largura zero e largura dupla) têm uma largura de 1, (obviamente) não lida com seqüências de escape, não é encapsulado adequadamente ... Isso provavelmente é destinado a terminais que não pode processar a guia.

De qualquer forma, a disciplina tty line precisa saber onde está o cursor e usa as heurísticas acima, porque ao usar o icanoneditor de linhas (como quando você digita texto para aplicativos como catesse não implementa seu próprio editor de linhas), quando você pressione TabBackspace, a disciplina de linha precisa saber quantos caracteres BS enviar para apagar esse caractere de tabulação para exibição. Se você alterar onde as paradas de tabulação estão (como em tabs 12), notará que as guias não são apagadas corretamente. O mesmo se você digitar caracteres de largura dupla antes de pressionar TabBackspace.


² Para isso, você pode enviar caracteres de tabulação e consultar a posição do cursor após cada um. Algo como:

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

Em seguida, você pode usar isso como expand -t "$tabs"usando a solução do @ Kusalananda.

Stéphane Chazelas
fonte
7
$ expand file | awk '{ print gsub(/ /, " ") }'
11
9
9

O expandutilitário POSIX expande guias para espaços. O awkscript conta e gera o número de substituições necessárias para substituir todos os espaços em cada linha.

Para evitar a contagem de espaços pré-existentes no arquivo de entrada:

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

onde @é um caractere garantido para não existir nos dados de entrada.

Se você quiser 10 espaços por guia, em vez dos 8 comuns:

$ tr ' ' '@' <file | expand -t 10 | awk '{ print gsub(/ /, " ") }'
9 
15
13
Kusalananda
fonte
3
Você gostaria de substituir espaços por outro caractere de uma largura (como x) antes de chamar o expandcontrário, também contaria os espaços que estavam inicialmente na entrada.
Stéphane Chazelas
1
expandtambém assume tab-stops a cada 8 colunas (embora você possa alterar isso com opções). Observe que a implementação GNU não suporta caracteres de vários bytes (muito menos caracteres de largura 0 ou largura dupla). O IIRC, o do FreeBSD, está bom.
Stéphane Chazelas
@ StéphaneChazelas A menos, claro, que faz parte do plano para contar a largura das 0x09s com os 0x20s ;-)
pode-ned_food
2

Com perl:

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

Alternativamente:

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

Você pode alterar 8 acima com algum outro valor se desejar que as TABs tenham um comprimento diferente.

Satō Katsura
fonte
2

Também usando expand, mas com manipulação de parâmetros bash para contar o número de espaços:

$ line=$'field0\tfield00\tfield000\tlast-field'
$ tabs2spaces=$(expand <<<"$line")
$ only_spaces=${tabs2spaces//[^ ]/}    # remove all non-space characters
$ echo "${#only_spaces}"
11
Glenn Jackman
fonte