Pimp my terminal

24 Sep, 2024 (updated: 24 Sep, 2024)
1977 words | 10 min to read | 5 hr, 56 min to write

So you wanna be a coder…
But your cat don’t fly
You gotta alias things up
To get that shell done right
Daaaaamn right…

This post is a tribute to a popular post by Remy Sharp, CLI: improved.

The de-facto standard unix commands such as ls, grep, top etc. are fine, they’ve been serving us well for a few decades. Even though those are the commands that are used daily by many, they are not the most user-friendly.

As someone who lives in terminal, I wanted my daily routine to be a little better, nicer, and overall more enjoyable. Bit by bit I tweaked the CLI experience to my liking by replacing some of the old-school commands with their modern alternatives.

TL;DR

How it works?

For instance, cat prints a file on your terminal. In many instances I use it to print files containing programming code, or some structured data such as JSON, Markdown, or XML. It would be quite helpful if cat main.go would do syntax hilighting, but cat doesn’t. What we can do is we can “replace” cat with a CLI utility that also prints files AND does syntax highlighting. I bet there must be a handful of such programms, but the most popular is bat, which is what I use. All that needs to be done is to install bat and make cat and alias to bat.

alias cat="bat"

so that when cat <file> is executed on the terminal, it’s bat that is executed instead of cat.

Drawbacks

Sometimes it’s not desireable to substitute the original command with the “improved” alternative. In such a case the original command can be executed in a couple of ways.

command cat <filename>

is the better option since it’s explicit and works across bash, zsh, and fish shells. This is my go-to.

Another option is to provide the full path to the original command, i.e.

/bin/cat <filename>

but it’s not as convenient because one must know where the original command resides on their system. And as such this is the least favourable method.

There is another way that only works in bash/zsh which is to add a backslash anywhere in the command like so:

\cat <filename>

this would’ve been my favourite, but as I said it’s bash/zsh only, and this is definitely not that killer feature that would make me switch from fish.

Having that said, there is another method that is available for me as fish user, which is to execute cat via bash using something like bax since cat is a fish alias, which bash has no idea about:

bax cat <filename> # something fishy this way comes ;-)

What I use

fish > zsh > bash

I used both bash and zsh for many years, but I switched to fish a few years ago and I’m not looking back. It’s fast, it’s user-friendly, and it’s pretty. I like the syntax and I have a bunch of scripts that I use daily that are written in fish.

There are some minor drawbacks, the main one is it’s not POSIX-compliant, has a little bit of learning curve, and it’s not as popular as bash or zsh, so it’s not always supported by default in some tools. But it’s popular enough to be used in those tools that I use, such as fzf, python’s virtualenv, starship etc.

image credit: fishshell.com

bat > cat

I use bat like this

# upgrade cat
if command -sq bat
alias cat="bat -pp"
alias caat="bat -n"
alias caaat="bat"
else
alias caat="cat -n"
alias caaat="cat -n"
end

By default I want it act like cat, but only give me syntax highlighting, no pager, no git integration, no line numbers. If I want line numbers, I use caat, and if I want the full bat experience, I use caaat.

bat > cat

htop > top

Everyone know about htop at this point, sometimes it’s one of the first things to be installed on a fresh production server. It provides very nice view of CPU and Memory at a glance, it’s pretty much all I need in most cases.

htop > top

fd > find

fd is like find, but requires less flags, and overall nicer interface. Just comapre:

find . -name "example.txt"
find docs -type f -name "*.md"
find . -name "*report*" -not -path "./backup/*"
find . -type d -name "config"
find . -type f -size +10M
find . -type f -size +1M -size -5M
find . -type f -mtime -7
find . -type f ! -newermt 2024-01-01
fd exmaple.txt
fd -e md -t f docs
fd report --exclude backup
fd -t d config
fd --size +10M
fd --size 1M..5M
fd --changed-within 7d
fd --changed-before 2024-01-01

Of course, the downside is if you already mastered your craft with find, or you have a buch of scripts wrapping find, using fd might be completely unnecessary, but I find even a simple case of finding files by name is much less mouthful comapred to find. I mean I just fd file, but I could as well had an alias for find like fnd = find . -type f , but I also used to find files by size, and the fd interface is just better.

ripgrep > grep

Similar to fd, ripgrep is like grep, but the interface is nicer, and the output too. Just like find, I still use grep, but in most cases all I need grep for is to rg DatabaseRepositoryInterface.

I tuned the default rg up like this though:

if command -sq rg
alias rg='rg --no-heading -M 150'
end

The -M flag truncates the output to 150 columns, otherwise if I have large generated files (such as minimised JS or CSS) the output blows up my terminal.

eza > ls

eza is just modern ls, let the comparison screenshot speak for itself:

eza > ls

This is my eza configuration:

# upgrade ls
if command -sq eza
alias ls='eza --time-style long-iso'
alias l='ls -la --icons'
alias la='ls -la'
alias lsd='ls -D'
alias lnew='l -snew'
alias lnewr='l -snew -r'
alias lanew='la -snew'
alias lanewr='la -snew -r'
alias tree='eza -T --icons'
else
alias l='ls -lA'
alias la='ls -lA'
alias lsd='ls -d *'
alias lnew='l -latr' # a bit quirky, I want new to be on the bottom, because the list may be long, I don't wanna scroll
alias lnewr='l -lat'
alias lanew='la -latr'
alias lanewr='la -lat'
end

zoxide > cd

zoxide keeps track of the directories you visit, and then you can jump to directories providing partial names.

Let’s say you have a bunch of projects in ~/projects and you want to jump to ~/projects/my-awesome-project, you can just type z my and it will take you there.

I use zoxide in conjunction with my p fish script which you can find here. The idea is quite simple, the script looks into predefined project directories and finds all the directories with a .git in them, and then I can jump to any of those directories by typing p <partial-project-name>. My own blog is located inside ~/Projects/coffeeaddict.dev/ on my machine, so I just type p coffee and I’m there. What happens behind the scenes is this:

if command -sq zoxide && test -n "$project"
set zoxide_match
for PROJECTS_DIR in $PROJECTS_DIRS
set search_term (string split "" $project)
set zoxide_match $zoxide_match (string collect (zoxide query "$PROJECTS_DIR" $search_term 2> /dev/null))
end
if test -n "$zoxide_match"
if test (count $zoxide_match) -gt 1
z (echo $zoxide_match | string split " " | fzf)
else
z $zoxide_match
end
return
end
end

which finds a zoxide match in any of the project directores, and then jumps to it.

If no match is found it lists all the projects using fzf and as soon as I select one I let zoxide to navigate to it.

if command -sq fzf
set -l source
set list
for PROJECTS_DIR in $PROJECTS_DIRS
set list $list (string collect (find $PROJECTS_DIR -maxdepth 3 -type d))
end
for dir in $list
if test -d "$dir/.git"
set -a source "$dir"
end
end
z (echo $source | string split " " | fzf)
else
zoxide > cd

I don’t replace cd as I replace ls or cat. Sometimes I don’t want magic, sometimes I don’t want zoxide to index the directory I’m navigating to, so I want both cd and z commands available, and if zoxide isn’t installed on my system, I fallback to cd:

# zoxide fallback
if not command -sq zoxide
function z -w cd
echo "zoxide is not installed! falling back to cd"
cd $argv
end
function zi -w cd
echo "zoxide is not installed! falling back to cd"
cd $argv
end
function zoxide -w cd
echo "zoxide is not installed! falling back to cd"
cd $argv
end
function za -w cd
echo "zoxide is not installed! falling back to cd"
cd $argv
end
function zq -w cd
echo "zoxide is not installed! falling back to cd"
cd $argv
end
function zr -w cd
echo "zoxide is not installed! falling back to cd"
cd $argv
end
end

fnm > nvm

fnm is just a MUCH faster nvm drop-in replacement. I hate having nvm.sh loading every time I create new shell session, which I do a lot. It irks me every time to experience the delay. fnm is just a binary, it doesn’t need to be hooked up into ~/.bashrc or ~/.zshrc or ~/.config/fish/config.fish or whatever. It’s just there, and it’s fast.

fzf

I LOVE fzf. It deserves a separate article. As soon as I found it it became indespensable for me. I use it for everything. I use it both inside shell and in vim. In a nutshell, it’s a fuzzy finder over lists of anything.

fzf in shell and vim

starship

Best description of what starship is is on their website:

Starship is the minimal, blazing fast, and extremely customizable prompt for any shell! Shows the information you need, while staying sleek and minimal.

I use it in fish, and it’s just perfect. It’s fast, it’s pretty, it’s customizable.

You might already seen what my prompt looks like from this very article, so I won’t post a screenshot here xD

tldr (not really) > man

tldr isn’t really a man replacement, but it’s a nice addition. It provides a quick overview of a command, and it’s community-driven, so it’s not as detailed as man, but it’s much more readable.

tldr

jq

jq is like sed + awk for JSON. By now, everyone knows about jq, so let’s move on onto gron which is less known.

jq
jq

gron

jq is powerful, but together with gron they are are ultimate combo!

gron

monoid

I tried, I really tried many fonts (one of my favs are IBM Plex Mono, Jetbrains Mono, Hack, Iosevka), but I always come back to monoid. It’s funky, it’s clumsy, it’s not perfect. It’s charming. And it’s perfect for coding.

monoid

tomorrow night

Same with my font of choice, I tried many themes. I like gruvbox, I like nord, I like snazzy, I like oceanicnext, but tomorrow night feels like home. And guess what? I ported it to my blog ;)