18 out 2026 · linux, terminal, produtividade · 6 min
Como deixar o terminal do Linux bonito e útil
Um script de 100 linhas levou meu prompt de 83 ms para 195 ms e me deu três comandos que eu não tinha. A parte que me segurou não foi o visual: foi um compinit que eu escrevi a mais e que custou 2,2 segundos em cada abertura de terminal.
Shell bonito é barato. Shell bonito que derruba a abertura do shell em dois segundos é o que a maioria dos tutoriais instala sem medir.
O script
O terminal-zsh.sh abaixo instala o zsh pelo gerenciador de pacotes da sua distro, clona o oh-my-zsh, habilita três plugins e escreve um .zshrc novo. O arquivo antigo vira .zshrc.bak-AAAAMMDD-HHMMSS antes de qualquer escrita, e o script sai com código 0 mesmo numa máquina sem rede, onde ele escreve só o que dá para escrever e avisa o que faltou.
#!/usr/bin/env bash
# terminal-zsh.sh - instala zsh, oh-my-zsh e tres plugins, e prova que o shell abre.
#
# Uso:
# ./terminal-zsh.sh instala e configura para o usuario atual
# ./terminal-zsh.sh --verificar nao instala nada, so confere o que existe
# ./terminal-zsh.sh --medir mede quanto tempo o shell leva para abrir
set -u
DATA=$(date +%Y%m%d-%H%M%S)
ZSH_DIR="${ZSH:-$HOME/.oh-my-zsh}"
RC="$HOME/.zshrc"
BAK="$RC.bak-$DATA"
PLUGINS="git z extract"
MODO="instalar"
case "${1:-}" in
--verificar) MODO="verificar" ;;
--medir) MODO="medir" ;;
"") MODO="instalar" ;;
*) printf 'uso: %s [--verificar|--medir]\n' "$0"; exit 2 ;;
esac
log() { printf '%s\n' "$*"; }
tem_cmd() { command -v "$1" >/dev/null 2>&1; }
# Rede sem depender de ping: abre uma conexao e fecha. Sem resposta, 4s e volta.
tem_rede() { timeout 4 bash -c 'exec 3<>/dev/tcp/github.com/443' >/dev/null 2>&1; }
gerenciador() {
local g
for g in apt-get dnf pacman apk; do
if tem_cmd "$g"; then printf '%s' "$g"; return 0; fi
done
return 1
}
instalar_pacotes() {
local g="$1" sudo_cmd=""
if [ "$(id -u)" -ne 0 ]; then
if ! tem_cmd sudo; then return 1; fi
sudo_cmd="sudo"
fi
# ca-certificates entra na lista porque sem o pacote store o git clone do
# github falha com "unable to access" em vez de "sem rede", e o script
# acaba achando que a rede caiu.
if [ "$g" = "apt-get" ]; then
$sudo_cmd apt-get update -qq >/dev/null 2>&1
$sudo_cmd apt-get install -y --no-install-recommends zsh git ca-certificates >/dev/null 2>&1
elif [ "$g" = "dnf" ]; then
$sudo_cmd dnf install -y zsh git ca-certificates >/dev/null 2>&1
elif [ "$g" = "pacman" ]; then
$sudo_cmd pacman -S --noconfirm --needed zsh git ca-certificates >/dev/null 2>&1
elif [ "$g" = "apk" ]; then
$sudo_cmd apk add --no-cache zsh git ca-certificates >/dev/null 2>&1
else
return 1
fi
}
instalar_omz() {
if [ -f "$ZSH_DIR/oh-my-zsh.sh" ]; then
log "oh-my-zsh: ja instalado em $ZSH_DIR"
return 0
fi
if ! tem_cmd git; then
log "oh-my-zsh: git ausente, pulei (o framework exige git)"
return 1
fi
if ! tem_rede; then
log "oh-my-zsh: sem rede para o github, pulei"
return 1
fi
git clone --depth=1 https://github.com/ohmyzsh/ohmyzsh.git "$ZSH_DIR" >/dev/null 2>&1
if [ -f "$ZSH_DIR/oh-my-zsh.sh" ]; then
log "oh-my-zsh: instalado em $ZSH_DIR"
return 0
fi
log "oh-my-zsh: o clone falhou, segui sem o framework"
return 1
}
# Configuracao com o framework: os tres plugins, prompt com branch e historico
# grande sem repeticao. O bloco do framework fica no topo porque plugins e tema
# sao lidos na hora do source.
config_com_omz() {
local p
{
printf 'export ZSH="%s"\n' "$ZSH_DIR"
printf 'ZSH_THEME="robbyrussell"\n'
printf 'plugins=(\n'
for p in $PLUGINS; do printf ' %s\n' "$p"; done
printf ')\n'
printf 'source $ZSH/oh-my-zsh.sh\n'
cat <<'ZSHRC'
# historico: 50 mil linhas, sem comando repetido em cima do anterior
HISTSIZE=50000
SAVEHIST=50000
HIST_IGNORE_ALL_DUPS=1
HIST_IGNORE_SPACE=1
# completacao: o framework ja chama compinit, entao reaproveitamos o dump dele
autoload -Uz compinit && compinit -d "$ZSH_COMPDUMP"
# git: gs, ga, gc (commit), gca (commit com amend) e gl (log curto)
alias gcam='git commit -am'
# z: pula para diretorio pelo nome, com frequencia
# extract: abre .tar.gz, .zip, .7z e companhia com um comando
ZSHRC
} > "$RC.tmp"
}
# Configuracao sem o framework: mesmos atalhos, na dependencia. Serve para o
# .zshrc nao ficar apontando para um source que nao existe.
config_sem_omz() {
{
printf '# zsh sem oh-my-zsh: atalhos minimos, sem source quebrado\n'
printf 'HISTSIZE=50000\nSAVEHIST=50000\nHIST_IGNORE_ALL_DUPS=1\n'
printf 'autoload -Uz compinit && compinit -d "$HOME/.zcompdump"\n'
printf 'alias gcam=%s\n' "'git commit -am'"
printf 'alias gs=%s\n' "'git status -sb'"
printf 'PROMPT=%s\n' "'%F{cyan}%~%f %# '"
} > "$RC.tmp"
}
escrever_rc() {
if [ -f "$RC" ]; then
cp -p "$RC" "$BAK"
log "backup: $RC -> $BAK"
fi
if [ -f "$ZSH_DIR/oh-my-zsh.sh" ]; then
config_com_omz
else
config_sem_omz
fi
mv "$RC.tmp" "$RC"
log "escrito: $RC"
}
conferir_plugins() {
local pachados=0 p
for p in $PLUGINS; do
if [ -d "$ZSH_DIR/plugins/$p" ]; then
pachados=$((pachados + 1))
else
log "plugin ausente: $p"
fi
done
printf 'plugins: %s de %s presentes\n' "$pachados" "$(printf '%s\n' $PLUGINS | wc -l | tr -d ' ')"
}
provar_shell() {
if ! tem_cmd zsh; then
log "zsh: nao instalado, a prova de abertura ficou para depois"
return 1
fi
zsh --version
if ! zsh -n "$RC" 2>/dev/null; then
log "erro de sintaxe no .zshrc: o shell nao abre com esse arquivo"
return 1
fi
log "sintaxe do .zshrc: ok"
if zsh -i -c 'printf "shell abriu com %s\n" "$ZSH_VERSION"' 2>/dev/null; then
log "shell interativo: respondeu"
else
log "shell interativo: falhou ao iniciar (rode zsh -i e veja o erro)"
fi
}
# Media de cinco aberturas, em milissegundos. O primeiro arranque fica de fora
# porque ele paga o cache frio e distorce a media.
medir_partida() {
local i t0 t1 soma=0 total=5
if ! tem_cmd zsh; then
log "zsh ausente: sem o que medir"
return 1
fi
zsh -i -c 'exit 0' >/dev/null 2>&1
for i in 1 2 3 4 5; do
t0=$(date +%s%N)
zsh -i -c 'exit 0' >/dev/null 2>&1
t1=$(date +%s%N)
soma=$(( soma + (t1 - t0) / 1000000 ))
done
printf 'abertura do shell: media de %s execucoes, %s ms\n' "$total" "$(( soma / total ))"
}
if [ "$MODO" = "instalar" ]; then
if ! tem_cmd zsh; then
if g=$(gerenciador); then
log "zsh ausente: instalando com $g"
if instalar_pacotes "$g" && tem_cmd zsh; then
log "zsh instalado: $(zsh --version)"
else
log "zsh nao instalado (sem root ou sem rede): o resto do script continua"
fi
else
log "zsh ausente e sem gerenciador de pacotes conhecido"
fi
else
log "zsh: $(zsh --version)"
fi
instalar_omz || true
escrever_rc
fi
if [ "$MODO" != "instalar" ]; then
log "modo $MODO: nada foi instalado nem reescrito"
[ -f "$RC" ] && log ".zshrc encontrado" || log ".zshrc ausente"
[ -f "$ZSH_DIR/oh-my-zsh.sh" ] && log "oh-my-zsh encontrado" || log "oh-my-zsh ausente"
fi
[ -f "$ZSH_DIR/oh-my-zsh.sh" ] && conferir_plugins
provar_shell || true
[ "$MODO" = "medir" ] && medir_partida || true
log "fim: abra um terminal novo e rode zsh"
exit 0
Os três plugins não são nada de mágico. git traz os atalhos curtos (gst para status, gca para commit com amend), z troca cd /caminho/longo por z relatorios, e extract abre .tar.gz, .zip e .7z com um comando em vez de quatro. É a diferença entre digitar e executar.
Como verificar
Rode ./terminal-zsh.sh e depois abra um terminal novo. A saída de uma execução completa num contêiner ubuntu 24.04 foi esta:
zsh ausente: instalando com apt-get
zsh instalado: zsh 5.9 (x86_64-ubuntu-linux-gnu)
oh-my-zsh: instalado em /root/.oh-my-zsh
escrito: /root/.zshrc
plugins: 3 de 3 presentes
zsh 5.9 (x86_64-ubuntu-linux-gnu)
sintaxe do .zshrc: ok
shell abriu com 5.9
shell interativo: respondeu
fim: abra um terminal novo e rode zsh
plugins: 3 de 3 presentes e shell interativo: respondeu são as duas linhas que importam. A primeira diz que os três plugins existem mesmo; a segunda diz que um shell interativo de verdade sobe com esse arquivo, o que é diferente de o arquivo ter sintaxe válida.
O compinit que eu escrevi a mais
O oh-my-zsh já chama compinit sozinho, na linha 238 do oh-my-zsh.sh, apontando para o dump $ZSH_COMPDUMP. Quando eu escrevi um segundo compinit com o caminho que todo tutorial mostra, compinit -d ~/.zcompdump, o shell foi de 195 ms para 2414 ms.
A causa é que o segundo dump não tinha para que ser reaproveitado. Conferi o arquivo entre duas aberturas: os 49699 bytes foram reescritos, enquanto o dump do framework permaneceu igual. Um compinit cujo arquivo é reconstruído a cada abertura faz o trabalho caro toda vez. Apontando para $ZSH_COMPDUMP, o mesmo comando custa 214 ms.
O .zshrc do script usa compinit -d "$ZSH_COMPDUMP", e é por isso que ele não cai nessa armadilha.
Se der errado
O clone falha e o script diz que a rede caiu. Com --no-install-recommends, o pacote ca-certificates fica de fora e o git falha com unable to access em vez de erro de DNS. Por isso ca-certificates está na lista de pacotes do script.
A abertura ficou lenta depois de alguma edição. Rode ./terminal-zsh.sh --medir e veja o número. Se ele passou de 300 ms, quase sempre é um compinit duplicado ou um compinit apontando para um caminho novo.
O .zshrc ficou com source para um arquivo que não existe. Acontece quando o clone falhou depois que o arquivo antigo foi movido para o backup. O backup está em ~/.zshrc.bak-AAAAMMDD-HHMMSS; para desfazer, copie-o de volta. O script também tem caminho sem framework, que escreve prompt e atalhos sem o source.
oh-my-zsh contra as alternativas
| opção | instalação | o que entrega | custo medido |
|---|---|---|---|
| zsh puro | gerenciador de pacotes | prompt, histórico, completar | 83 ms |
| zsh + oh-my-zsh | clone do git | 372 plugins, 140 temas, autocomplete | 195 ms |
| starship | binário único no cargo | prompt rápido, sem framework | não medido aqui |
| bash + bash-it | clone do git | plugins de bash | não medido aqui |
Starship é o competidor mais sério: um binário, sem os 16 MB do clone. Oh-my-zsh ganha em plugin pronto, perde em velocidade de partida e ocupa mais disco. Se o que você quer é só um prompt bonito, o starship resolve em uma linha.
O resto da máquina também ganha com o script. Vale a mesma regra de deixar as peças se manterem sozinhas em vez de depender de alguém lembrar de rodar o chsh.
FAQ
Preciso do oh-my-zsh para ter os três plugins? Não. git e extract existem como alias e função de poucas linhas, e o z pode ser o script de 20 linhas do próprio projeto. O framework é conveniência, não dependência.
O script roda em Arch e Fedora? Sim, ele detecta apt-get, dnf, pacman e apk e usa o caminho de instalação de cada um. Não testei os quatro, testei o do apt.
Trocar o shell padrão é automático? Não. O script não mexe no /etc/passwd de propósito. Rode chsh -s "$(command -v zsh)" quando quiser, e confira com echo $SHELL numa sessão nova.
O robbyrussell é o melhor tema? É o mais seguro: usa caracteres ASCII e não precisa de fonte com Powerline. Os temas com setas e ícones exigem uma fonte instalada antes, e é aí que a maioria trava achando que quebrou o zsh.
Fontes
- ohmyzsh/ohmyzsh, repositório e versão com 372 plugins, licença MIT, leitura em 18 de outubro de 2026.
- Instalando o ZSH, wiki do projeto, com os comandos por distro, atualizado em 28 de julho de 2025.
- Lista de plugins do oh-my-zsh, onde
git,zeextractaparecem com o README de cada um.
Medições em contêiner descartável ubuntu 24.04, com 0,5 CPU e 256 MB de RAM: zsh 5.9, oh-my-zsh clonado com --depth=1, mediana de sete aberturas de shell depois de uma de aquecimento. Detalhes de método e as sete leituras de cada ponto estão no post sobre chaves SSH, que mediu da mesma forma.