Optimiser sa productivité en console

Publié le 13 septembre 2011 par Nicolas Cavigneaux | outils

Cet article est publié sous licence CC BY-NC-SA

J’ai l’intime conviction que tous les développeurs, dès que cela est possible, devraient travailler sous un environnement dérivé d’Unix (Linux, *BSD, Mac OS X) ce qui assure d’avoir à disposition tous les outils nécessaires au développement mais aussi de pouvoir faire la plupart des manipulations directement depuis l’éditeur ou un terminal.

En effet lorsque je développe, je passe une bonne partie du temps dans un terminal pour des opérations aussi diverses que variées. Certains d’entre vous rétorqueront qu’un IDE adapté permettra de tout faire sur un projet donné sans lâcher la souris. Il s’avère que je ne suis pas du tout fan des IDE pour trois raisons :

  • la lenteur / lourdeur générale des IDE
  • la quasi obligation d’utiliser la souris et donc de lâcher le clavier
  • l’impossibilité de facilement automatisé / personnaliser des tâches récurrentes

En ce qui me concerne, l’environnement idéal est Mac OS X, Textmate et Zsh. Je vais tenter de vous expliquer en quoi cet environnement peut se révéler très productif et addictif.

Pourquoi Zsh ?

La plupart des développeur que je connais utilisent Bash soit par choix, par habitude ou tout simplement parce qu’il est défini en temps que shell par défaut du système.

Pour ma part j’ai fait le choix de Zsh pour ses grandes capacités de complétion des commandes, sa gestion de l’historique avancée et sa facilité d’extension.

Les exemples et techniques que je vais vous présenter ne fonctionneront peut-être pas directement sous un autre shell (bash / ksh / …) mais resteront quoi qu’il en soit valides sur le principe et facilement adaptable à votre shell favori.

Je conseille vivement à ceux qui ne connaissent pas Zsh et qui n’ont encore aucun shell de prédilection (pas d’habitudes d’utilisation ni de config aux petits oignons) de démarrer avec celui-ci. Zsh est facile d’accès et l’essayer c’est l’adopter !

Personnaliser son prompt

Le prompt est la ligne de commande vide qui vous attend après chaque commande entrée. Elle peut être très simple en n’affichant que le nom d’utilisateur courant. Ce prompt peut également être très évolué en vous fournissant des informations en fonction du contexte.

Le mien par exemple m’affiche :

  • le répertoire courant
  • le type de SCM (git / hg / svn) utilisé dans ce répertoire
  • l’état du dépôt
  • la version courante de Ruby
  • le gemset actuel

Personnalisation ZSH

Tout cela peut paraître bien superflux mais quand on passe ses journées dans la console ou l’éditeur de texte, il faut s’épargner le plus de frappe possible pour être plus productif et réduire la fatigue.

Grâce à mon prompt, dans un répertoire classique, rien ne s’affiche hormis le répertoire courant. Dans un projet Rails, versionné à l’aide de git et utilisant RVM, je sais en un coup d’oeil le chemin courant, le SCM utilisé, la branche active, les éventuelles modifications (fichiers non-commités, commit en retards, …), la version de ruby utilisée et le gemset s’il y en a un d’actif !

Vous pouvez me croire sur parole, sur une semaine, le nombre de commandes tapées est considérablement réduit. Un plaisir pour les doigts.

Plus besoin de tout vérifier ou retenir quand on jongle d’un terminal à l’autre, ces informations sont sous les yeux.

Pour construire mon prompt je me suis basé sur la librairie Oh My ZSH qui permet d’importer tout un tas de fonctionnalités pratiques (librairies / plugins / thèmes).

Une fois Oh My ZSH installé, les bons plugins chargés, voici ce qu’il m’a fallût faire pour créer mon thème :

#RVM settings
if [[ -s ~/.rvm/scripts/rvm ]] ; then
  RPROMPT="%{$fg[yellow]%}\$(~/.rvm/bin/rvm-prompt i v g)%{$reset_color%}"
fi

ZSH_THEME_GIT_PROMPT_PREFIX="%{$reset_color%}%{$fg[green]%}[git:"
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$fg[green]%}]%{$reset_color%} "
ZSH_THEME_GIT_PROMPT_DIRTY=""
ZSH_THEME_GIT_PROMPT_CLEAN=""

ZSH_THEME_GIT_PROMPT_ADDED="%{$fg[green]%}✚%{$reset_color%}"
ZSH_THEME_GIT_PROMPT_DELETED="%{$fg[red]%}✗%{$reset_color%}"
ZSH_THEME_GIT_PROMPT_MODIFIED="%{$fg[blue]%}!%{$reset_color%}"
ZSH_THEME_GIT_PROMPT_RENAMED="%{$fg[red]%}➦%{$reset_color%}"
ZSH_THEME_GIT_PROMPT_UNTRACKED="%{$fg[red]%}?%{$reset_color%}"
ZSH_THEME_GIT_PROMPT_UNMERGED="%{$fg[red]%}↓%{$reset_color%}"

PROMPT='%{$fg[cyan]%}%~% %{$reset_color%}
$(git_prompt_info)$(git_prompt_status)%B$%b '

Plutôt concis non ?

Avoir un prompt bien configuré pour son utilisation peut se révéler être un atout précieux au quotidien.

Configurer son shell

Au delà de la configuration de prompt, il est important de personnaliser le comportement du shell pour qu’il s’adapte au mieux à vous et non pas l’inverse.

Complétion

Les shells sont capables de prédire et corriger vos commandes avec plus ou moins de succès. Zsh excelle dans ce domaine et permet une configuration très fine du comportement. Voici un extrait de ma configuration de complétion de mon shell :

unsetopt menu_complete   # Ne sélectionne pas automatiquement la première entrée
setopt auto_menu         # Affiche le menu de complétion après 2 appuis sur tab
setopt complete_in_word  # Autorise la complétion à l'intérieur d'un mot
setopt always_to_end     # Envoi le curseur en fin de ligne aprés complétion


zmodload -i zsh/complist # Charge les complétions disponibles
zstyle ':completion:*' list-colors '' # On active la coloration dans les complétion, notamment pour ls


zstyle ':completion:*:*:*:*:*' menu select # On affiche un menu pour la complétion plutôt que de boucler sur les possibilités
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;34=0=01' # Coloration des process en cas de complétion
zstyle ':completion:*:*:*:*:processes' command "ps -u `whoami` -o pid,user,comm -w -w" # La complétion ne propose que les process m'appartenants

Voici les résultats de différentes complétions :

Commande “ls” Complétion ls

Commande “git” Complétion GIT

Commande “kill” Complétion kill

Correction

Un deuxième allié peut être la correction des commandes. Attention, la correction automatique ne plaît pas à tout le monde … Utilisant ZSH, j’ai tout le loisir de configurer le comportement de la correction en fonction du contexte et de la commande. Voici un extrait de ma configuration :

# On corrige tout ce qui peut l'être : commandes, nom de fichiers, urls, …
setopt correct_all

# Pour les commandes suivantes je ne veux pas de correction
alias man='nocorrect man'
alias mv='nocorrect mv'
alias mysql='nocorrect mysql'
alias mkdir='nocorrect mkdir'
alias gist='nocorrect gist'
alias heroku='nocorrect heroku'
alias ebuild='nocorrect ebuild'
alias hpodder='nocorrect hpodder'

Se soulager les doigts avec les alias

Il y des commandes que l’on tape des dizaines de fois dans la journée. De longues commandes avec parfois des options à retenir. N’importe qui utilisant un shell remarque rapidement qu’il y a des optimisations à faire de ce côté.

Heureusement les alias viennent à la rescousse pour améliorer notre confort et épargner notre mémoire. Je vais ici vous lister les alias qui me servent le plus souvent. N’oubliez pas que nous avons chacun notre façon de travailler, n’hésitez donc pas à personnaliser ces alias ou même à en créer d’autres.

# Navigation

alias -- -='cd -'
alias ..='cd ..'
alias ...='cd ../..'
alias cd..='cd ..'
alias cd...='cd ../..'
alias cd....='cd ../../..'
alias cd.....='cd ../../../..'
alias cd/='cd /'

alias history='fc -l 1' # Historique des commandes avec numéros de rappel des commandes

alias lsa='ls -lah'
alias l='ls -la'
alias ll='ls -l'
alias sl=ls # Faute de frappe courante

alias _='sudo' # Super user

## Ruby / Rails

alias be="bundle exec"
alias bi="bundle install"
alias bl="bundle list"
alias bu="bundle update"
alias bp="bundle package"

alias rst="touch tmp/restart.txt" # Redémarrer Pow / Passenger / …
alias devlog='tail -f log/development.log'

alias sgem='sudo gem' # Installer des gems en tant que root
alias rfind='find . -name *.rb | xargs grep -n' # Recherche d'un mot dans les fichiers Ruby

alias rubies='rvm list rubies'
alias gemsets='rvm gemset list'

# Textmate

alias et='mate .'
alias ett='mate app config lib db public spec test Rakefile Capfile Todo' # Projet Rails
alias etp='mate app config lib db public spec test vendor/plugins vendor/gems Rakefile Capfile Todo'  # Projet Rails avec le répertoire vendor
alias etts='mate app config lib db public script spec test vendor/plugins vendor/gems Rakefile Capfile Todo' # Projet Rails avec les répertoires vendor et script

alias mr='mate README app config db lib public script spec test' # Projet Ruby

## Git

alias g='git'
alias gst='git status'
alias gl='git pull'
alias gup='git fetch && git rebase'
alias gp='git push'
alias gc='git commit -v'
alias gca='git commit -v -a'
alias gco='git checkout'
alias gb='git branch'
alias gba='git branch -a'
alias gcount='git shortlog -sn' # Nombre de commits par personne
alias gcp='git cherry-pick'
alias glg='git log --stat --max-count=5'
alias glgg='git log --graph --max-count=5'
alias gss='git status -s'
alias ga='git add'

Aller plus loin grâce aux fonctions

Les fonctions permettent d’aller plus loin puisqu’elles nous permettent de scripter des commandes et donc de rendre leur comportement dynamique. Plus difficile à mettre en œuvre qu’un simple alias mais bien plus puissant.

Pour commencer une commande pratique qui permet de créer un répertoire puis de s’y rendre en une commande :

function mcd() {
  mkdir -p "$1" && cd "$1";
}

function tm() {
  cd $1
  mate $1
}

On utilise le même principe pour se rendre dans un répertoire donné puis l’ouvrir avec Textmate

Bundler

Bundler est un utilitaire de gestion des dépendances bien pratique qui en plus fonctionne avec RVM. Malgré cela, dans un projet géré avec bundler et RVM, il faut parfois faire attention à lancer les commandes via bundler. En ce qui me concerne j’oublis toujours et je me retrouve à chercher pourquoi un gem n’est pas détecté …

Voici un ensemble de fonctions qui vont rendre toutes ces opérations transparente :

# Commandes devant être exécutées via Bundler
bundled_commands=(cap capify cucumber guard heroku rackup rails rake rspec ruby shotgun spec spork thin unicorn unicorn_rails)

# Vérifie la dispo de Bundler
_bundler-installed() {
  which bundle > /dev/null 2>&1
}

# Vérifie si le répertoire courant est un projet utilisant bundler
_within-bundled-project() {
  local check_dir=$PWD
  while [ "$(dirname $check_dir)" != "/" ]; do
    [ -f "$check_dir/Gemfile" ] && return
    check_dir="$(dirname $check_dir)"
  done
  false
}

# Lance une commande via bundler si il est installé et que le projet l'utilise
_run-with-bundler() {
  if _bundler-installed && _within-bundled-project; then
    bundle exec $@
  else
    $@
  fi
}

# Pour chaque commande devant être géré via bundler, on crée un alias qui utilise les routines précédentes
for cmd in $bundled_commands; do
  alias $cmd="_run-with-bundler $cmd"
done

Maintenant quelque soit le répertoire, bundler ou non, il suffira de taper la commande habituelle et bundle exec sera appelé en arrière plan si nécessaire. Une fois encore on gagne des caractères à frapper mais surtout plus besoin de réfléchir. L’environnement est consistant quelque soit l’endroit où l’on se trouve.

Rails

Je vous ai déjà présenté quelque alias concernants Rails mais le meilleur reste à venir via les fonctions. Une fois encore, on va s’épargner des réflexions inutiles et des frappes de caractères :

# Commandes devant être exécutées via Bundler
bundled_commands=(cap capify cucumber guard heroku rackup rails rake rspec ruby shotgun spec spork thin unicorn unicorn_rails)

# Vérifie la dispo de Bundler
_bundler-installed() {
  which bundle > /dev/null 2>&1
}

# Vérifie si le répertoire courant est un projet utilisant bundler
_within-bundled-project() {
  local check_dir=$PWD
  while [ "$(dirname $check_dir)" != "/" ]; do
    [ -f "$check_dir/Gemfile" ] && return
    check_dir="$(dirname $check_dir)"
  done
  false
}

# Lance une commande via bundler si il est installé et que le projet l'utilise
_run-with-bundler() {
  if _bundler-installed && _within-bundled-project; then
    bundle exec $@
  else
    $@
  fi
}

# Pour chaque commande devant être géré via bundler, on crée un alias qui utilise les routines précédentes
for cmd in $bundled_commands; do
  alias $cmd="_run-with-bundler $cmd"
done

Git

Nous avons déjà définis de nombreux alias pour Git mais il reste quelques améliorations possibles grâce à une nouvelle fonction et quelques alias :

# Retourne le nom de la branche courante
function current_branch() {
  ref=$(git symbolic-ref HEAD 2> /dev/null) || return
  echo ${ref#refs/heads/}
}

# Alias utilisant la fonction précédente
alias ggpull='git pull origin $(current_branch)'
alias ggpush='git push origin $(current_branch)'
alias ggpnp='git pull origin $(current_branch) && git push origin $(current_branch)'

Une fois de plus des commandes raccourcies et moins de choses à garder en tête.

Conclusion

Avoir un shell qu’on maitrise, bien configuré, personnalisé en fonction de son utilisation se révèle être un réel avantage au quotidien. Vous y gagnez en confort et en rapidité.

Les quelques exemples présentés ici ne sont qu’un tout petit aperçu de ce qu’on pourrait mettre en œuvre pour optimiser sa productivité dans le terminal.

Pour ceux qui souhaitent profiter facilement de tout ce dont j’ai pu parler dans cet article, je ne saurai trop vous conseiller d’installer le couple Zsh / Oh My Zsh. La configuration principale se résumera à charger les modules dont vous avez besoin et choisir un thème. Libre à vous ensuite de peaufiner mais vous aurez déjà une base plus que solide et exhaustive.

L’équipe Synbioz.

Libres d’être ensemble.