Upgrading your dev environment is the quickest win to be more productive. A trustworthy environment is like shopping in your nearby supermarket for the hundredth time. You know exactly where to find what you need. Everything feels familiar and diligently arranged. On the contrary, an unreliable environment feels like being lost in the endless alleys of a crowded souk where each shopkeeper screams for your attention.
Improving a dev environment is an exploratory process. Start from a blank file, then tour all blog posts and GitHub repos like a curious traveler anxious to miss any hidden gem. Ask your friends and colleagues for recommendations. They will proudly share their favorite Bash scripts and browser extensions. Yet exploring doesn't mean strolling without direction, so prioritize. Track down your most irritating config bugs [1]. Identify your most frequent boring actions then hunt the tool to automate them. If you fall down a rabbit hole, settle on a first version of your config. It won't be perfect, but will deliver value immediately.
Document all instructions to reproduce your environment from scratch meticulously. There is no room for doubt at any step. Even dragging a slider in a Preferences window must be described. Store the instructions in a Git repo along with all configurations. You might realize later that you forgot some steps. When they cross your mind, note them down immediately and make a point to update your repo by end of day. Technical debt is real for dev environments too. If you lack rigor, you're up for guaranteed frustration.
Like a shopping list, this dev environment is yours. No general-purpose config from the Internet will ever meet all your expectations. Focus on your own unique needs. If you can't memorize some allegedly cool alias, ditch it without regret then craft your own. Complicated solutions are oftentimes rigid, so prefer widely adopted solutions that you understand clearly. Keep it simple and embrace minimal configuration.
Your dev environment is your ideal supermarket. Its aisles feel familiar. They prominently display the products you love. You can stick to your shopping list and pick up that spicy barbecue sauce that only you can eat. You can breeze to the cashier, or maybe check out the latest trends. But whatever you decide, you always come back home swiftly with everything you need to cook a delicious meal.
[1] With my Swiss keyboard, the backspace key in the micro editor was duplicating lines instead of forward-deleting. Typing was awful.
I use a Mac Book Pro at work and a desktop computer with Ubuntu 20.04 at home. However, most of the terminal tools I use are common to both operating systems.
I use ZSH with ZSH-completions, as well as Oh My ZSH. The oh-my-zsh wiki is worth reading to learn about commonly used aliases (e.g. ... => cd ../..) and optional plugins that might be of interest to you. If you have been using Bash with little customization, give a try to ZSH with Oh My ZSH and you might be surprised by how good it is.
zprof is a profiling tool integrated into ZSH. Add zmodload zsh/zprof at the top of your ~/.zshrc, then start a new shell and run zprof. This will display a breakdown of the shell's loading time. Thanks to zprof, you can discover which parts of your .zshrc slow down your shell.
For oh-my-zsh, my ~/.zshrc's plugins=() array has the following:
CTRL+O.At the end of my ~/.zshrc, two new files ~/.zshrc_custom.zsh and ~/.secrets.sh are sourced:
~/.zshrc_custom.zsh contains all my init commands for various tools, as well as aliases~/.secrets.sh contains API keys, passwords, etc... I separate secrets from the other files to reduce the risk of committing them by accident.To quickly open a text editor for the files above, I use these aliases:
alias zshc="$EDITOR ~/.zshrc_custom.zsh"
alias zshs="$EDITOR ~/.secrets.sh"
Here are a few notes based on my previous experience:
.zshrc, as I always prefer to re-spawn the current tmux pane instead to get a clean environment.~/.zprofile nor ~/.zshenv for my init commands and aliases and prefer to keep everything in ~/.zshrc_custom.zsh for simplicity. If ~/.zshrc_custom.zsh becomes very big, it can be split up into smaller files to be sourced sequentially.~/.zshrc or other files. I always move those to ~/.zshrc_custom.zsh so everything stays in the same place.I use tmux with gpakosz's configuration to use multiple terminals in a single display. gpakosz's config improves a lot the default tmux config with better graphics and information about the system (uptime, hostname, etc...)
At the end of my .tmux.conf.local created as part of gpakosz config setup, I have re-mapped some of the keys for my Swiss keyboard and added some new shortcuts:
bind k split-window -h
bind l split-window -v
unbind '"'
unbind %
bind j select-pane -t :.+
unbind o
bind n previous-window
bind m next-window
unbind p
# Replace current pane by a new one
bind h respawn-pane -k
# Capture current pane content to buffer
bind s capture-pane -S -1000 \; save-buffer /tmp/tmux-buffer.txt \; send-keys tmuxbuffer Enter\
In my ~/.zshrc_custom.zsh, I have these functions:
function tmuxbuffer () {
# Open tmux buffer saved with PREFIX+S shortcut in text editor
local TMUX_BUFFER="/tmp/tmux-buffer.txt"
if [ ! -f "$TMUX_BUFFER" ]; then
echo "[ERROR] Could not find file: $TMUX_BUFFER"
return 1
fi
sed -i -e :a -e '/^\n*$/{$d;N;};/\n$/ba' "$TMUX_BUFFER"
"$EDITOR" "$TMUX_BUFFER"
}
dev-tmux () {
# Create a new tmux session with some windows opened at specific locations
# that I need frequently
local TMUX_SESSION_NAME="dev-tmux"
tmux new-session -d -s "$TMUX_SESSION_NAME" -n "firstwindow"
tmux new-window -t "${TMUX_SESSION_NAME}:" -n "todaydirs" -c "${TODAYDIRS}"
tmux new-window -t "${TMUX_SESSION_NAME}:" -n "my-frequent-dir-1" -c "${WORKSPACE_DIR}/frequent-dir-1"
tmux new-window -t "${TMUX_SESSION_NAME}:" -n "my-frequent-dir-2" -c "${WORKSPACE_DIR}/frequent-dir-2"
# Repeat at needed
tmux kill-window -t "${TMUX_SESSION_NAME}:firstwindow"
tmux select-window -t "${TMUX_SESSION_NAME}:todaydirs"
tmux attach-session -d
}
SSH is pretty ubiquitous, so I use the ~/.ssh/config file extensively to create aliases for hosts I access often. I also use the ProxyJump option to simplify accessing hosts located behind a bastion host.
When I have poor connectivity to my target such as in the train or when accessing a machine far away, I use mosh as a substitute for SSH. Note that as of now, mosh does not support ProxyJump.
I use ed25519 to generate my SSH key as it is more secure and results in a shorter public key. My public key is named after its creation date and the device such as 20211223-Pierre-Desktop so it can easily be recognized by other people who don't know if they have my most recent public key.
# SSH into a machine without the host key check (avoid "someone is doing something nasty" error)
alias sshi="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
# Copy my public key to clipboard to share it quickly
alias pk="cat $HOME/.ssh/id_ed25519.pub | tr -d '\n' | clipcopy"
# Open my SSH config
alias sshc="$EDITOR $HOME/.ssh/config"
# Open my known-hosts file
alias sshkh="$EDITOR $HOME/.ssh/known_hosts"
# Open my authorized_keys file
alias sshak="$EDITOR $HOME/.ssh/authorized_keys"
# List authorized_keys names / comments
alias sshakl="cat ~/.ssh/authorized_keys | grep -Eo ' \S+\$' | grep -Eo '\S+'"
ls with better defaults and optionstree with an interactive file explorerfind with better defaults and is fasterCTRL+T) and previously used command (CTRL-R), all with autocompletion..envrc file in the current directory when you enter it. Instead of cluttering and slowing down ~/.zshrc_custom.zsh with project-specific init commands and aliases, I can put them in the .envrc at the root of the project that needs them.nano due to its mouse support and its default shortcuts (e.g. CTRL+C, CTRL+V). I have export EDITOR=micro to set micro as my default editor in my ~/.zshrc_custom.zsh.~/.config/micro/bindings.json contains {"Ctrl-d": "Delete"} to prevent the backspace issue described in the "Motivation" section.grep that is faster and works with regexes by default.sed to perform text substitution with the regex syntax of Pythoncat with syntax highlighting. I have cat aliased to bat in my ~/.zshrc_custom.zsh.top to visualize the resource usage of processes, including a tree view. I have aliased htop to alias htop="htop --tree" in my ~/.zshrc_custom.zsh so it shows the tree view by default.du to show a breakdown of the disk usage of a set of files/directoriesdf to show system-wide disk usageThis is a list of aliases that replace some of the most used commands by their modern alternative, or that add some more sensible defaults.
# Always enable colored "grep" output
alias grep='grep --color=auto '
# copy/move ask for permission
alias cp='cp -i'
alias mv='mv -i'
# on macOS
# alias sed="gsed"
# alias date="gdate"
# Modern tools replace old ones
alias cat="bat --paging never --plain"
alias catp="bat"
# If we need to unalias entries from common-aliases from Oh-My-ZSH
# for COMMAND in l ll la; do unalias \$COMMAND; done
alias ls='exa' # ls
alias l='exa -lbFa --git' # list, size, type, git
alias ll='exa -lbGFa --git' # long list
alias llm='exa -lbGda --git --sort=modified' # long list, modified date sort
alias la='exa -lbhHigUmuSa --time-style=long-iso --git --color-scale' # all list
alias lx='exa -lbhHigUmuSa@ --time-style=long-iso --git --color-scale' # all + extended list
In addition, I use tldr to print more user-friendly man pages.
I only use Git for version control. On some projects that involve large amounts of test data (e.g. video data for computer vision), I set up Git LFS.
To get better diffs with Git, I use diff-so-fancy. To visualize changes in a nice terminal GUI, I use lazygit with alias lg=lazygit.
For Git-specific aliases, I use the ~/.gitconfig file:
[user]
name = My Name
email = myemail@example.com
[init]
defaultBranch = master
[pull]
rebase = false
[submodule]
recurse = true
# Integration with diff-so-fancy
[core]
pager = diff-so-fancy | less --tabs=4 -RFX
[interactive]
diffFilter = diff-so-fancy --patch
[alias]
# Committing
ap = add .
cm = commit -m
cmwip = !git add -A && git commit -m "WIP"
amend = commit --amend
unstage = reset HEAD
undo = reset HEAD~1
undo-hard = reset --hard HEAD~1
cp = cherry-pick
# Branches
s = status
f = fetch
pl = pull
co = checkout
cob = checkout -b
sync-master = !git checkout master && git pull --rebase
# History
rbi = rebase -i
rbm = rebase origin/master
rbmi = rebase -i origin/master
rbn = rebase origin/main
rbni = rebase -i origin/main
pushf = push --force-with-lease
rho = !git reset --hard origin/$(git rev-parse --abbrev-ref HEAD)
# Diffs
d = diff --patch-with-stat
dc = diff --cached
di = !"d() { git diff --patch-with-stat HEAD~$1; }; git diff-index --quiet HEAD -- || clear; d"
# Lists
tags = !git tag -l | cat
branches = !git branch -a | cat
latest-branches = !git branch -a --sort=-committerdate | head -n 20
remotes = !git remote -v | cat
latest = !sh -c 'git log origin/master --pretty=format:%H --reverse | head -1'
unreleased = "!f() { git fetch --tags && git diff $(git tag | tail -n 1); }; f"
graph = log --graph --color --pretty=format:"%C(yellow)%H%C(green)%d%C(reset)%n%x20%cd%n%x20%cn%x20(%ce)%n%x20%s%n"
aliases=!git config -l | grep ^alias | cut -c 7- | sort
# Find
find-branches-by-commit = "!f() { git branch -a --contains $1; }; f"
find-tags-by-commit = "!f() { git describe --always --contains $1; }; f"
find-commits-by-source = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short -S$1; }; f"
find-commits-by-message = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short --grep=$1; }; f"
VS Code is my main text editor. I use its remote development features extensively, including Remote - SSH. In addition to the most common language- and tool-specific plugins (e.g. Docker, Prettier, Better TOML), I use the following general-purpose plugins:
git blame output for the currently active lineHere are my config changes from the defaults:
{
"telemetry.enableCrashReporter": false,
"telemetry.enableTelemetry": false,
"editor.minimap.enabled": false,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"editor.rulers": [89]
}
Here are some aliases I have to open VS Code from the terminal:
alias vsc="code ."
alias vscn="code --new-window"
alias vscr="code --reuse-window"
For HTTP and DNS, I use the following:
curldigI use Docker extensively to work with containers. For visualization purposes, I use the following tools:
Python is the main programming language I have been using the past few years. I used to manage my Python interpreters and virtual environments with pyenv, but have recently written my own tool to replace it: lonesnake.
I use lonesnake to create a global Python environment for my user account, and then also to create local environments for each of my Python projects. In my global environment, I use pipx to install the command-line tools that happen to be written in Python.
Instead of working inside ~/Documents or ~/Desktop or ~/Downloads, I keep everything in a ~/workspace directory.
Within ~/workspace, I have a special directory called dailydirs containing 1 directory for each day with my work files for that day. This is easier to manage than keeping all files in the ~/Downloads directory.
In addition, I have a few aliases such as todaydir to access the workspace or daily directories.
WORKSPACE_DIR="$HOME/workspace"
alias wks="cd \"$WORKSPACE_DIR\""
mkdir -p "$WORKSPACE_DIR"
DAILY_DIRS="${WORKSPACE_DIR}/dailydirs"
mkdir -p "$DAILY_DIRS"
alias todaydir='mkdir -p "${DAILY_DIRS}/$(date +%Y%m%d)-dailydir/" && cd "${DAILY_DIRS}/$(date +%Y%m%d)-dailydir/"'
Given that I have a lot of configuration and several dotfiles, my dev environment is a great candidate for version control. chezmoi provides user-friendly commands to manage dotfiles with Git and to "apply" them to a new computer in seconds. My dotfiles are stored in my dotfiles repo on GitHub.
In terms of desktop apps, I use:
On Ubuntu, I use xclip to copy to and from the clipboard with my aliases.
On macOS, I have a few specific apps:
CTRL+OPTION+LEFT, right half as CTRL+OPTION+RIGHT, previous display as CTRL+OPTION+CMD+LEFT, next display as CTRL+OPTION+CMD+RIGHT, full screen as CTRL+OPTION+F