Elmord's Magic Valley

Software, lingüística e rock'n'roll. Às vezes em Português, sometimes in English.

The art of the prompt string

2012-05-06 03:49 -0300. Tags: comp, unix, bash, em-portugues

Ofereço este post com conteúdo útil como sacrifício aos leitores, de modo a ter crédito para escrever posts reclamando da vida mais tarde.

O prompt padrão do bash na maior parte das distribuições de GNU/Linux costuma ser algo do tipo username@hostname diretório$ . Esse prompt pode ser customizado alterando-se o valor das variáveis PS1 ("prompt string 1") e companhia:

vitor@eukleides ~$ PS1='oi? '
oi? pwd
/home/vitor
oi? 

Você pode experimentar prompts diferentes alterando essa variável até achar um que seja do seu agrado, e então alterar um dos arquivos de configuração do bash (e.g., ~/.bashrc) para setar a variável para o valor desejado na inicialização do shell.

Backslash sequences

Como você pode imaginar, o conteúdo de PS1 não precisa ser texto estático. O bash substitui certas seqüências de \ seguido de um caractere (e.g., \u) na definição do prompt por valores específicos (e.g., o nome do usuário). O prompt padrão apresentado acima pode ser obtido pela string \u@\h \W\$ :

Uma lista completa das seqüências expandidas pelo bash no prompt pode ser obtida procurando por PROMPTING no manual (man bash). Outras seqüências úteis incluem \w (caminho completo do diretório atual) e \! (número do comando atual no histórico; útil para quem gosta de usar os comandos de recuperação do histórico, e.g., !42 para reexecutar o comando de número 42).

Escape sequences

Uma das maravilhas do terminal (que talvez seja digna de um post a respeito no futuro) são as escape sequences: seqüências de caracteres especiais que podem produzir toda sorte de firula, desde cores e outros efeitos de texto até alterações no título da janela de terminal. Por exemplo, a seqüência ESC [ 3 número m, onde ESC é o caractere ASCII número 27, seleciona a cor de texto com o número especificado (que varia de 0 a 7); a seqüência ESC [ 0 m retorna as cores e outros atributos para seus valores padrão. O caractere ESC pode ser representado no prompt pela seqüência \e. Assim:

PS1='\e[34m\u@\h \W\$ \e[0m'

lhe dará um prompt idêntico ao padrão, mas em azul. Existe uma manpage (man console_codes) com uma lista de seqüências ESC suportadas por diversos terminais.

Só tem um problema: o bash usa o tamanho da string de prompt expandida para calcular o tamanho que o prompt ocupa na tela; ele precisa dessa informação para determinar onde ocorre a quebra de linha, caso o comando digitado possua mais de uma linha, e para saber onde posicionar o cursor caso você tecle Home, entre outras coisas. O problema é que os caracteres de uma seqüência ESC não ocupam espaço na tela, mas aumentam o tamanho da prompt string. Para o bash poder calcular corretamente o tamanho do prompt, é necessário demarcar os trechos de seqüências ESC no prompt entre \[ e \]:

PS1='\[\e[34m\]u@\h \W\$ \[\e[0m\]'

Provavelmente a coisa mais útil que se costuma fazer com seqüências ESC no prompt é setar o título da janela de terminal para conter o diretório atual:

PS1='\[\e]0;\w\a\]\u@\h \W\$ '

ESC ] 0 ; título BEL é a seqüência que muda o título da janela do xterm e outros terminais gráficos. (BEL, representado no prompt por \a, é o caractere ASCII número 7; em situações normais, ele é o caractere que faz o terminal emitir um beep.)

Se você nem sempre acessa o terminal via interface gráfica, pode ser uma boa idéia testar se o terminal atual é gráfico antes de adicionar seqüências ESC no prompt:

case "$TERM" in
    xterm*|gnome*|konsole*) PS1='\[\e]0;\w\a\]\u@\h \W\$ ' ;;
    *) PS1='\u@\h \W\$ ' ;;
esac

Ou, alternativamente, para evitar duplicação:

# Primeiro adiciona a seqüência ESC.
case "$TERM" in
    xterm*|gnome*|konsole*) PS1='\[\e]0;\w\a\]' ;;
    *) PS1='' ;;
esac

# Depois, o resto do prompt (igual em ambos os casos).
PS1+='\u@\h \W\$ '

Variáveis

Além de backslash sequences, o bash substitui variáveis presentes na prompt string. Por exemplo, PS1='$USER@$HOSTNAME $PWD\$ ' tem mais ou menos o mesmo efeito que PS1='\u@\h \w\$ '. Ao se usar variáveis no prompt, é importante usar aspas simples em volta do valor que está sendo atribuído a PS1:

O bash também expande comandos (usando a sintaxe $(comando) ou `comando`) presentes na string de prompt. Novamente, é importante cuidar para usar aspas simples caso se queira que o comando execute toda vez que o prompt é impresso, e não apenas uma vez durante a definição do prompt. De modo geral, executar um comando (criar um processo) cada vez que o prompt é impresso não me parece uma boa idéia; normalmente, executar o comando uma vez só resolve a maior parte dos problemas e é menos custoso computacionalmente. (Se bem que hoje em dia criar um processo provavelmente não vai fazer uma diferença palpável na execução do shell ou do sistema; eu recomendaria não fazer isso com o prompt do root, entretanto. A gente nunca sabe quando vai ter que matar uma fork bomb ou um processo mal-comportado no sistema.)

Uma utilidade de usar variáveis e/ou comandos é incluir nome da tty atual no prompt. Isso é particularmente útil quando se está trabalhando no modo texto, pois pelo nome da tty sabe-se o número do console virtual (e lembrando o número pode-se rapidamente voltar para o console em questão teclando Alt-Fn).

tty="$(tty)"                # Recupera o nome completo da tty (e.g., /dev/tty1)
if [[ $tty == */pts/* ]]; then
    ttybase="pts${tty##*/}" # Transforma /dev/pts/0 em pts0
else
    ttybase="${tty##*/}"    # Transforma /dev/tty1 em tty1
fi

PS1='\u@$ttybase \W\$ '     # Usa o nome da tty ao invés do hostname.

Ganhamos o nome da tty, mas perdemos o hostname. Uma possibilidade é testar se a conexão atual é via ssh, e usar o hostname nesse caso:

tty="$(tty)"
if [[ $SSH_CONNECTION ]]; then
    ttybase="$HOSTNAME"
elif [[ $tty == */pts/* ]]; then
    ttybase="pts${tty##*/}"
else
    ttybase="${tty##*/}"
fi

PS1='\u@$ttybase \W\$ '

Outra informação útil de se incluir no prompt (uma que eu já não consigo viver sem, a ponto de alterar o prompt de outras máquinas quando vou usá-las por mais do que alguns minutos) é o exit status do último comando. O exit status é um valor ente 0 e 255 que cada processo retorna ao terminar. (É por isso que a main() retorna int, e não void, em C.) Por convenção, um programa retorna 0 se foi bem-sucedido, e um valor diferente de zero caso tenha ocorrido um erro (valores diferentes podem ser usados para erros diferentes). O exit status do último comando vive na pseudo-variável $?:

PS1='\u@$ttybase \W($?)\$ '

A presença do exit status no prompt é útil por mil razões. Primeiro, se você costuma escrever shell scripts, freqüentemente deseja saber que valor um comando retorna em certa situação. Além disso, o exit status presente no prompt dá um feedback imediato quanto ao sucesso ou falha do último comando, sem ter que ler toda a saída do comando. Por exemplo, ao copiar um diretório com muitos arquivos com cp -v, pode ser que uma mensagem de erro passe desapercebida no meio da saída normal do cp; com o exit status no prompt, o erro é imediatamente visível. Com o tempo, verificar o exit status é um ato inconsciente; para mim, hoje em dia, ele é um feedback tão importante quanto a saída do comando. Finalmente, existem alguns programas cretinos que não imprimem mensagens de erro, apenas retornam 1 para indicar que falharam. (Lamentavelmente, com o exit status no prompt, você possivelmente se sentirá inclinado a escrever programas que fazem o mesmo. Eu não tenho o direito de atirar pedras.)

The prompt command

A última maravilha promptística que tenho a apresentar é a variável PROMPT_COMMAND: se essa variável for setada, seu valor é interpretado como um comando a ser executado antes de imprimir o prompt. Você pode usá-la para chamar alguma função que seta uma variável usada na string do prompt.

Eis uma possibilidade: nos doces tempos do bash 1.14, o comportamento do \W era diferente: não havia abreviação do diretório home para ~; além disso, se o diretório estivesse diretamente abaixo do raiz (e.g., /mnt), o bash imprimia a / no início do nome, e não apenas mnt. O comportamento mudou no bash 2, e me foi a mi mui mal. Felizmente, é possível corrigir esse problema usando o PROMPT_COMMAND e uma variável no prompt:

reduced_pwd() {
    case "$PWD" in
        /*/*) REDPWD="${PWD##*/}" ;;
        *) REDPWD="$PWD" ;;
    esac
}

PROMPT_COMMAND="reduced_pwd"
PS1='\u@$ttybase $REDPWD($?)\$ '

Pode-se adaptar a reduced_pwd, com uma pequena dose de falcatrua, para mostrar os últimos dois itens do diretório atual, ao invés do caminho completo ou do último item:

reduced_pwd() {
    local prefix
    case "$PWD" in
        /*/*/*)
            # Se PWD = /mnt/foo/bar/baz/quux:
            prefix="${PWD%/*/*}"        # prefix = /mnt/foo/bar
            REDPWD="${PWD#"$prefix"/}"  # REDPWD = baz/quux
            ;;
        *)
            REDPWD="$PWD"
            ;;
    esac
}

Pode-se obter a temperatura da CPU, ou a carga da bateria da máquina, a partir dos arquivos em /sys. Pode-se baixar periodicamente informação climática de algum site e adicionar a temperatura ao prompt, mudando a cor dependendo de se o tempo está ensolarado ou nublado ou chuvoso. As possibilidades são infinitas.

Comentários / Comments (0)

Deixe um comentário / Leave a comment

Main menu

Posts recentes

Comentários recentes

Tags

em-portugues (213) comp (137) prog (68) in-english (50) life (47) pldesign (35) unix (34) lang (32) random (28) about (27) mind (25) lisp (23) mundane (22) fenius (20) ramble (17) web (17) img (13) rant (12) hel (12) privacy (10) scheme (10) freedom (8) copyright (7) bash (7) music (7) academia (7) lash (7) esperanto (7) home (6) mestrado (6) shell (6) conlang (5) emacs (5) misc (5) latex (4) editor (4) book (4) php (4) worldly (4) android (4) politics (4) etymology (4) wrong (3) security (3) tour-de-scheme (3) kbd (3) c (3) film (3) network (3) cook (2) poem (2) physics (2) wm (2) treta (2) philosophy (2) comic (2) lows (2) llvm (2) perl (1) en-esperanto (1) audio (1) old-chinese (1) kindle (1) german (1) pointless (1) translation (1)

Elsewhere

Quod vide


Copyright © 2010-2020 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.