TL;DR

  1. Modern Terminal with nice (Nerd) Fonts
  2. ZSH
  3. oh-my-zsh
  4. Modern Prompt with starship
  5. Modern CLI tools (fzf, ripgrep, bat, tmux, jq)
  6. Fast editor (Emacs, Neovim, Helix)
  7. Organize your dotfiles with GNU Stow

The Unix shell/Command-Line Interface (CLI) is an ancient tool. Despite being around forever, the shell is highly customizable and with a good configuration, it is on par with modern graphical IDEs. Because it is configurable, you can make it truly your own, thereby surpassing out-of-the-box experience of IDEs. The shell has existed for 50+ years, and since it’s highly adaptable, it’s not going anywhere which makes it a great knowledge investment for the future. No need to re-learn your toolset every few years. However, it comes with a catch, a full shell configuration takes time and effort. In this article, I want to show you how I configured my shell for ultimate productivity, so you don’t have to go same tiresome learning process as I did.

Use a modern Terminal

A good terminal application is the main entrance to your shell environment and crucial a top-notch developer experience. Recommended terminals are:

For a modern look-and-feel, I suggest Nerd Fonts. It comes with icons for Kubernetes, Python, Java, Golang, Rust which pretty up the CLI. For Ubuntu, you can install and enable Nerd Fonts with the following commands:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# download a specific font (or choose your favorite font)
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v2.2.2/DejaVuSansMono.zip

# unzip file
unzip DejaVuSansMono.zip

# move all files in fonts directory
mv *.ttf ~/.local/share/fonts/

# rebuild font cache, so that font will be available
fc-cache -fv

# afterwards you need to set the new font in you terminal app
# for Gnome Terminal, you can do it in the Menu Settings
# for Alacritty you need to adjust the config file, see below
1
2
3
4
5
6
7
8
# file ./config/alacritty/alacritty.yaml

font:
  normal:
    # Font family
    family: DejaVuSansMono NF
  # Point size
  size: 12.0

Switch to ZSH

Most Linux distribution use bash as the default shell. We want to switch to zsh which is more powerful than bash, but yet fully compatible to it. Newer MacOS versions come with zsh enabled. For Ubuntu you have to install zsh:

1
2
3
4
5
# install zsh
sudo apt install zsh

# make zsh the default shell
chsh -s /usr/bin/zsh

oh-my-zsh

With zsh installed, we paved the way for our customization endeavours. We are ready to install oh-my-zsh, a delightful, extensible, pre-configured zsh environment with reasonable defaults. It builds the base for further tailoring and provides a cheerful prompt with a lovely color theme. oh-my-zsh also defines a lot of useful aliases for git and other CLI tools which save

The main zsh configuration file is ~/.zshrc. There you can enable and disable oh-my-zsh plugins but also write your own aliases or activate subcommand completions for kubectl, git or terraform. You can go even further and write your own bash functions which are then available in you shell sessions. For example, I use functions to toggle between different java versions toggle_java8 and toggle_java17. Below you find a small section of my ~/.zshrc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# file: ~/.zshrc  (selected parts)

# oh-my-zsh location
export ZSH="$HOME/.oh-my-zsh"

# default oh-my-zsh prompt
ZSH_THEME="robbyrussell"

# enable plugins which provide aliases etc.
plugins=(git)

# enable oh-my-zsh
source $ZSH/oh-my-zsh.sh

# general stuff
export LC_ALL=en_US.UTF-8 # set default language settings
export LANG=en_US.UTF-8
export TERM=xterm-256color # enable true color support

# environment variables, used by CLI tools like git
export EDITOR="emacsclient -t"
export VISUAL="emacsclient -c"

# git will use emacs for commit messages, you can replace this with your favorite editor
export GIT_EDITOR="emacsclient -c"
export PAGER=less  # used by git commands in order to print git log, git diff outputs

# aliases
alias e='emacsclient -t'  # emacs is my favorite editor, so the alias is very short :)
alias ecc='emacsclient -c' # use emacs server instead of starting a new emacs process
alias ew='emacs -nw -q' # start clean emacs without loading any configuration
alias edd='emacs --daemon' # start emacs server in background -> instant emacs startup time
alias ag='ag --path-to-ignore ~/.agignore'
alias k='kubectl'         # short k8s commands
alias kd='kubectl describe'
alias t='terraform'       # short terraform commands
alias gdiff='git diff --no-index' # use git diff for non-versioned files

# kubectl subcommand completion
source <(kubectl completion zsh)

# terraform subcommand completion
autoload -U +X bashcompinit && bashcompinit
complete -o nospace -C $HOME/.local/bin/terraform terraform

# example function which shows memory usage of a process
# USAGE:
# $> mem emacs # shows the memory consumption of the emacs process
mem()
{
    ps -eo rss,pid,euser,args:100 --sort %mem | grep -v grep | grep -i $@ | awk '{printf $1/1024 "MB"; $1=""; print }'
}

starship

In the next step we configure our shell prompt. The default prompt only shows you the current working directory. As a cloud infrastructure engineer though, I work with Git, multiple K8s clusters, Azure Accounts and other different contexts. For that reason, I like to have all such information in my shell prompt. That proactively prevents executing commands in the wrong environment 😱.

The best prompt these days is starship. It’s stable, fast and easily configurable via a single starhship.toml file.

1
2
3
4
5
6
# file ~/.zshrc

# prerequisite: install starship, see installation instructions at webpage

# enable starship by putting this line at the end of the file
eval "$(starship init zsh)"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# file ~/.config/starship.toml

# set the color for current working directory
[directory]
style = "cyan bold"

# configure k8s context: the current cluster and the namespace will be shown in green
[kubernetes]
format = '[☸ ($cluster)\($namespace\)](green) '
disabled = false

# contexts for git, Azure/AWS/GCP are dected automatically
# starship even detects the programming language in your project directory
# you only need to add a section if you want to customize the defaults

My current prompt shows the current K8s cluster and namespace, the current git branch, the current active Python and the Rust versions with icons:

starship_shell_prompt

Modern CLI commands

We already made it very far and your terminal experience should have improved a lot by now. To get most out of the shell, you should master the pre-installed tools like find, grep, sed, ls, cd, cat, less etc. Additionally to the classic tools, modern alternatives exist which provides a contemporary user experience. Below you can find my favorite tools which have the biggest impact on my daily workflow:

  • fzf, an interactive shell command history search tool on steroids, it completely changed the way how I use the shell command history with CTRL-r

  • delta, a much nicer git diff, delta not only highlights which lines changed but also the exact location in a specific line

  • ripgrep or ag, both are grep alternatives, they are faster and provide a more convenient API, e.g they ignore common folders by default like .git or node_modules

  • bat, a cat alternative with syntax highlighting and pager features

  • zoxide, a smarter way to change directories, zoxide remembers nested paths and makes switching directories a breeze

  • tmux or zellij are terminal multiplexers. If you run long running terminal sessions or you work on remote machines, these tools are highly recommended

  • kubectx/kubens, kubectl commands can get very long, with kubectx/kubens you can pin the cluster and namespace for all future commands which saves a lot of typing

jq / yq

As a developer, you often consume JSON Rest-APIs. Writing a test for experimenting in your programming language can be cumbersome. A faster way is using curl and jq:

1
2
3
4
5
6
7
# curl | jq example

# curl makes an HTTP request

# jq consumes the response, formats it and provides mapping and filtering features for inspection
curl -s "https://jsonplaceholder.typicode.com/todos" \
| jq ".[]| {new_id: .id, new_title: .title}"

yq is the same as jq but for yaml files. A great facilitation if you work often with Kubernetes.

Shell command tips and tricks

The shell is a whole universe and it takes time to get familiar with it. Eventually, it is a rewarding experience which will be beneficial for the rest of your life. Unfortunately, there is no shortcut for learning the CLI. Anyway, I want to show you my most useful commands in order to give you an head start:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# run you last commands again with !!
ls -l
!!

# goto your $HOME directory
cd

# jump back to your last directory
cd -

# use the shell history with CTRL-r, fzf will give you a superior experience

# search-replace all occurences in all rust files recusively
find . -iname "*.rs" | xargs sed -i "s/mod/foobar/g"

# for MacOS, it's a bit different since MacOS does not use GNU Tools but the BSD Tools
find . -iname "*.rs" | xargs sed -i "" "s/mod/foobar/g"

# combine search-replace with git, i.e.
# 1. start from a clean git commit
# 2. execute search-replace
# 3. check the changes with git diff
# 4. if you don't like the result, do a git reset

# CTRL-k delete to the end of line

# CTRL-u delete line

# CTRL-a jump to start of line

# use TAB to trigger auto-completion

# cycle through all commands with CTRL-p and CTRL-n

Your Editor, choose wisely

Fist of all, I don’t want to trigger another editor war. Everyone can use the editor she prefers and makes her most productive.

Nevertheless, one’s editor is the main workhorse for developers who spend many hours per day in it. Thus a good editor is essential. For me a good editor must be able to run inside the shell, so I don’t need to switch between applications during my work session. Besides that, an editor should be fast. Contemporary editors should offer an instant experience opening and changing files. If you want to go down the rabbit whole, you can also start configuring your editor to your needs. My opinionated editor recommendations are:

Emacs

My favorite editor. I use it for almost everything: programming, note taking, visual git user interface, writing etc. It’s extremely configurable via Emacs Lisp. Sophisticated, stable plugins exist for every conceivable scenario, for example LSP support, Tree-sitter, Git etc. I run the awesome Emacs Prelude distribution. It builds the foundation for my own modifications.

Neovim

If you don’t like Emacs, Neovim is an alternative on eye level. As a member of the VIM family, Neovim provides the classic modal-editing experience. Neovim is also highly configurable via Lua. You can extend the editor via plugins, write your own config or just use a pre-configured distribution like AstroNvim which gives you an IDE-like feeling without much tinkering.

Helix

Helix is a blazing fast terminal editor written in Rust. Syntax-Highlighting and LSP support works out-of-the-box. I use it for short edits and Rust development. The great thing about Helix is that you get 80% of the Emacs or Neovim features with almost no customization effort.

A normal Emacs or Neovim config consists of thousand lines of code. In contrast, this is my complete helix configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
theme = "onedark"

[editor]
line-number = "relative"
mouse = false

[editor.cursor-shape]
insert = "bar"
normal = "block"
select = "underline"

[editor.file-picker]
hidden = false

[editor.auto-pairs]
'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'
'<' = '>'

GNU Stow

Although we are done with the shell configuration, we have now a lot of dotfiles lying around in the $HOME directory. A good way to manage them would be git. Unluckily, we cannot put our $HOME directory in a git repository. Here GNU Stow comes into play. GNU Stow uses symbolic links to manage a group of dotfiles inside a single folder. This is exactly what we want. Let’s go through a simple example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# create dotfiles folder somewhere
mkdir $HOME/dotfiles

cd dotfiles/

# move our .gitconfig into the new dotfiles/ folder
mkdir git
mv $HOME/.gitconfig git/

# inside dotfiles/, it should look like this:
tree git -a
git
└── .gitconfig

# lets create a symlink with GNU Stow
stow --verbose --target=$HOME git

# .gitconfig is now available at $HOME/.gitconfig

# deleting the symbolic link is also possible
stow --verbose --target=$HOME --delete git

If you want to learn how to symlink whole nested directory structures or just dive deeper, you can look into this great step-by-step guide.

Conclusion

The shell is ultimately customizable - use it to your advantages. It will make you more productive and work will be more enjoyable. Thereby the configuration process is never ending, you can always squeeze out more and optimize your environment. I hope this article gave you some valuable insights and ideas how to improve your productivity. And maybe you open your IDE less often 🙃

References

The Unix Programming Environment