Como encontro a largura e a altura de uma janela de terminal?


295

Como um exemplo simples, quero escrever um script CLI que possa ser impresso =em toda a largura da janela do terminal.

#!/usr/bin/env php
<?php
echo str_repeat('=', ???);

ou

#!/usr/bin/env python
print '=' * ???

ou

#!/usr/bin/env bash
x=0
while [ $x -lt ??? ]; do echo -n '='; let x=$x+1 done; echo

Eu criei este minúsculos node.js lib para obter consistentemente o correto janela de tamanho npmjs.com/package/window-size
jonschlinkert

Respostas:


562
  • tput cols informa o número de colunas.
  • tput lines informa o número de linhas.

11
echo -e "lines\ncols"|tput -Spara obter ambas as linhas e cols ver: linux.about.com/library/cmd/blcmdl1_tput.htm
nickl-

4
tputé um ótimo comando com muitos comandos para ler o estado do terminal, controlar as propriedades do cursor e do texto e assim por diante.
de Drew Noakes

2
Alias ​​útil, por exemplo:, alias dim="echo $(tput cols)x$(tput lines)"que pode resultar em 80x50.
bispo

2
Essas perguntas e respostas provavelmente pertencem aos sites SE de unix ou superusuário.
Mydoghasworms

2
@bishop o comando alias que você forneceu é avaliado quando o shell é obtido. Você precisa usar aspas simples para o comando alias. Assim: alias dim='echo Terminal Dimensions: $(tput cols) columns x $(tput lines) rows'
brandonsimpkins

103

No bash, as variáveis ​​ambientais $LINESe $COLUMNSdevem ser capazes de executar o truque. O valor será definido automaticamente após qualquer alteração no tamanho do terminal. (ou seja, o sinal SIGWINCH )


18
No entanto, essas variáveis ​​de ambiente estão disponíveis apenas para o bash, e não para os programas executados no bash (como perl, python, ruby).
Br.Bill

9
Isso não funciona em nada além da sessão interativa do bash (se você executar o script, ele não será mais interativo). O único lugar em que você pode usá-lo em um script é o prompt_command no bash.
Doncho Gunchev

1
Na verdade, ele funciona em scripts não interativos, se você definir a checkwinsizeopção. Por exemplo, este script não interativo imprimirá as dimensões do terminal no qual é executado: shopt -s checkwinsize; (:); echo $LINES $COLUMNS(a checkwinsizeopção inicializa apenas as variáveis ​​após aguardar a conclusão de um subshell, e é por isso que precisamos da (:)instrução)
user3340662

$LINESe $COLUMNSsão atualizados após o SIGWINCHenvio, na verdade, após qualquer execução de comando interativa. Se você tentar atualizar PS1com trap SIGWINCHvocê não pode usar $LINESe $COLUMNS, eles mantêm valores antigos ((
gavenkoa

LINESe COLUMNSsão definidas apenas como variáveis ​​de shell por bash. O Bash não os definirá como variáveis ​​de ambiente , a menos que você exporte essas variáveis ​​de shell.
Markus Kuhn

67

E há stty, a partir de coreutils

$ stty size
60 120 # <= sample output

Ele imprimirá o número de linhas e colunas, ou altura e largura, respectivamente.

Então você pode usar cutou awkpara extrair a parte que deseja.

Isso é stty size | cut -d" " -f1para a altura / linhas e stty size | cut -d" " -f2para a largura / colunas


Esse estilo não pode funcionar com o PIPE, sugira usar o estilo tput.
liuyang1

6
o problema com o tput é que ele nem sempre está disponível enquanto o stty está disponível em todos os tty. obrigado por essa informação!
iRaS 23/09

16
yes = | head -n$(($(tput lines) * $COLUMNS)) | tr -d '\n'

3
Não é uma resposta direta à pergunta, mas um ótimo script de demonstração.
Chris Página

Que ótimo exemplo!
Kurt Zhong

1
como diabos eu perdi o trcomando todos esses anos? (facepalm)
Marco Massenzio

Que língua é essa? Parece vagamente um script de shell com erros de sintaxe. (No shell você não pode ter espaços em torno da atribuição de sinal de igual, e o primeiro tubo parece fora do lugar.)
tripleee

2
yes '='produzirá uma quantidade infinita de linhas '=' e os seguintes comandos se organizarão o suficiente para preencher o terminal
pixelbeat

12

Para fazer isso no ambiente da CLI do Windows, a melhor maneira de encontrar é usar o comando mode e analisar a saída.

function getTerminalSizeOnWindows() {
  $output = array();
  $size = array('width'=>0,'height'=>0);
  exec('mode',$output);
  foreach($output as $line) {
    $matches = array();
    $w = preg_match('/^\s*columns\:?\s*(\d+)\s*$/i',$line,$matches);
    if($w) {
      $size['width'] = intval($matches[1]);
    } else {
      $h = preg_match('/^\s*lines\:?\s*(\d+)\s*$/i',$line,$matches);
      if($h) {
        $size['height'] = intval($matches[1]);
      }
    }
    if($size['width'] AND $size['height']) {
      break;
    }
  }
  return $size;
}

Espero que seja útil!

NOTA : A altura retornada é o número de linhas no buffer, não é o número de linhas visíveis na janela. Alguma opção melhor por aí?


3
Observe um problema com isso: a saída deste comando é específica do local. Em outras palavras, isso não funcionará como está em outro código do idioma do Windows. Isto é o que eu recebo no Windows 7: i.imgur.com/Wrr7sWY.png
Camilo Martin

Adicionada uma resposta com uma solução para isso. +1 embora!
Camilo Martin

10

No POSIX, em última análise, você deseja chamar a chamada TIOCGWINSZ(Get WINdow SiZe) ioctl(). A maioria dos idiomas deve ter algum tipo de invólucro para isso. Por exemplo, no Perl você pode usar o Term :: Size :

use Term::Size qw( chars );

my ( $columns, $rows ) = chars \*STDOUT;

1
Obrigado por isso - me levou na direção certa. Elixir: :io.columnsErlang: io:columns(). erlang.org/doc/man/io.html#columns-0
Henrik N

2
Não existe TIOCGWINSZno padrão POSIX e ioctl()é definido apenas para o recurso STREAMS obsoleto.
osvein

4

Como mencionei na resposta do lyceus, seu código falhará no código de idioma do Windows que não é o inglês, pois a saída de modepode não conter as subseqüências "colunas" ou "linhas":

                                         saída do comando mode

Você pode encontrar a substring correta sem procurar texto:

 preg_match('/---+(\n[^|]+?){2}(?<cols>\d+)/', `mode`, $matches);
 $cols = $matches['cols'];

Observe que eu nem estou me incomodando com as linhas porque não é confiável (e eu realmente não me importo com elas).

Edit: De acordo com comentários sobre o Windows 8 (oh você ...), acho que isso pode ser mais confiável:

 preg_match('/CON.*:(\n[^|]+?){3}(?<cols>\d+)/', `mode`, $matches);
 $cols = $matches['cols'];

Mas teste, porque eu não testei.


Seu método não funciona no Win8. Eu recebo mais de uma ---linha. i.imgur.com/4x02dqT.png
MPEN

@ Mark Bem, ótimo, isso é apenas BONITO. Obrigado Windows. <3 (em uma nota mais relevante: vou ver como corrigir isso ... quando o Windows 9 for lançado: P).
Camilo Martin

Esta é a maneira que eu fazê-lo: $mode = `mode`; list($rows, $cols) = array_slice(preg_split('/\n/', substr($mode, strpos($mode, 'CON:'))), 2, 2);. E então eu apenas substituo tudo, menos os números.
Aleksandr Makov 16/09

@AleksandrMakov Gostaria de saber o que acontece se houver idiomas com ordem como CON device status:? Talvez combinar algo como CON.*:funcionaria melhor.
Camilo Martin

1
@ Mark, eu estava realmente me questionando exatamente isso. Por que diabos eu fiz isso? Na dúvida, eu apenas assumi que havia algum motivo e segui-o, lol.
Camilo Martin

1

Inspirado na resposta de @ pixelbeat, aqui está uma barra horizontal criada por tput, um leve uso indevido de printfpreenchimento / preenchimento etr

printf "%0$(tput cols)d" 0|tr '0' '='

0

Existem alguns casos em que suas linhas / LINHAS e colunas não correspondem ao tamanho real do "terminal" que está sendo usado. Talvez você não tenha um "tput" ou "stty" disponível.

Aqui está uma função bash que você pode usar para verificar visualmente o tamanho. Isso funcionará até 140 colunas x 80 linhas. Você pode ajustar os máximos conforme necessário.

function term_size
{
    local i=0 digits='' tens_fmt='' tens_args=()
    for i in {80..8}
    do
        echo $i $(( i - 2 ))
    done
    echo "If columns below wrap, LINES is first number in highest line above,"
    echo "If truncated, LINES is second number."
    for i in {1..14}
    do
        digits="${digits}1234567890"
        tens_fmt="${tens_fmt}%10d"
        tens_args=("${tens_args[@]}" $i)
    done
    printf "$tens_fmt\n" "${tens_args[@]}"
    echo "$digits"
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.