This post is meant to be a brief introduction to some productivity tools that can make a command line workflow smoother and more efficient. While well known, standard tools with similar function already exist (e.g. bash, grep, find, etc.), the tools presented hereafter have a handy user interface with sensible default behaviour, simpler options that are easy to remember, and are optimised for speed, making them useful tools for interactive usage, especially when working on large projects.
It must be noted that such tools are not standardised, and hence less portable than their POSIX counterparts (grep, find, etc.), so they should never replace standard command line utilities in the context of shell scripting.
What follows is only a collection of suggestions for a general purpose workflow, and it is not supposed to be an exhaustive list, moreover the tools mentioned here are not silver bullets, but only a few among many other valuable alternatives available in their respective categories.
Fzf
Fzf is a fuzzy search tool for the command line. Fuzzy matching allows to search for strings that correspond to a given pattern only approximately; with fzf, a string will match a pattern as long as it contains all the characters from the pattern, in the given order. This allows to find things very quickly, with a limited number of keystrokes (if you are not familiar with this search approach, it will become straightforward after a minimal amount of practice, and then you will never look back…). Fzf is optimised for speed and it works well even when dealing with very large text corpora.
Fzf can be used to search for files, file content, processes, variables, and more. Default shell key bindings (for bash, Zsh, and fish) are available to search for files and folders (ctrl + t), commands from the history (ctrl + r), and fuzzy change directory (alt + c). Integrations with the major text editors exist, and fzf can be easily used as a base for custom tools (many examples from the community are available).
Key bindings can be enabled by sourcing the provided install scripts, e.g. for
Zsh source in .zshrc
as follows:
# The exact path may depend on your installation
. /usr/share/fzf/key-bindings.zsh
. /usr/share/fzf/completion.zsh
Ripgrep
Ripgrep (rg) is a line search tool
that recursively searches for regex patterns within files, with a behaviour
similar to grep -r
. It has a simple and convenient syntax, and by default it
respects .gitignore
, ignores hidden files, and offers coloured output. It
supports Perl regular expressions (PCRE2), non-UTF-8 text encodings, search
inside compressed archives, and input preprocessing filters. It is optimised
for speed and it can be significantly
faster than GNU grep when searching
through large text corpora.
A large amount of introduction material can be found in the readme, user guide, and FAQ pages.
It must be noted that ripgrep is not the only alternative to GNU grep, and a number of well-known grep-like tools exist, such as git grep, ack, ucg, sift, ag, and pt. A helpful summary can be found in a comparison table compiled by Andy Lester, main author of ack.
GNU Parallel
GNU Parallel is a tool to run
multiple jobs in parallel. It can take commands to be run either from arguments
or from the standard input, with a syntax similar to xargs
. The simplest use
case is to provide a list of commands in input, and Parallel will take care of
distributing them between different processes:
cat list_of_jobs | parallel
If the number of jobs exceeds the number of available workers (by default equal
to the number of CPUs), Parallel will organise a queue. Among the basic
options, it is possible to specify the desired number of workers with the
--jobs
argument, and it is even possible to distribute jobs across different
machines via ssh
, using the --sshlogin
flag.
Parallel does not just allow to run commands, but also to build them, taking
one or multiple lists of arguments and allowing to combine them. A list of
arguments is always introduced by the separator :::
. For instance, the
following is a trivial example that outputs all possible binary numbers of
three digits, by feeding all possible combinations of 0
and 1
as arguments
to echo
:
$ parallel echo ::: 0 1 ::: 0 1 ::: 0 1
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
Parallel is a powerful and feature-rich tool, that goes well beyond the scope of this post. The official tutorial is a good starting point to get familiar with its functionalities.
Fd
Fd is a file search tool, with a use case
similar to find
. Compared to the latter, it offers a very handy syntax: a
simple search by file name inside the current folder can be done by just
writing fd foo
, whereas a minimal find command would look like find . -name
foo
.
Fd has sensible defaults oriented towards interactive use (smart-case, coloured
output, respects .gitignore
, ignores hidden files and directories), it offers
Unicode support, and optimisation for speed (especially when performing
regex-based search, it can be significantly faster than find
).
It has built-in support for parallel execution, with a syntax similar to GNU Parallel. For instance, the following command finds all JPEG images and converts them to PNG in parallel
fd -e jpg -x convert {} {.}.png
and it is equivalent to
find . -name '*.jpg' | parallel convert {} {.}.png
Fd can be used in combination with other tools, for instance as default input source for fzf by setting the following environment variable:
export FZF_DEFAULT_COMMAND='fd --type f'
Command auto-correction
Any command line user has experienced countless times the mixed feeling of surprise and frustration when a command fails because of a typo. Or because of a forgotten sudo before some administrative command requiring root privileges. Some shells are able to detect certain categories of mistakes and suggest a possible correction, for instance Zsh can correct typos in commands and arguments for some commands, after enabling the relative options:
setopt correct # Enable built-in command auto-correction
setopt correctall # Enable argument auto-correction
At this point, the interaction will look like this:
Auto-correction can be however rather inaccurate when enabling setopt
correctall
, and sometimes it may wrongly suggest to replace an actually
correct argument with a file name that looks similar. Moreover, this mechanism
can only catch a limited amount of mistakes, for instance it cannot help in
presence of a misspelled git command. Luckily, git offers its own
auto-correction mechanism, that can be enabled in .gitconfig
by assigning to
autocorrect
a value in tenths of seconds, representing a wait time before
actual auto-correction happens.
[help]
autocorrect = 20
Now the result of a misspelled git command will look like:
Unfortunately, not all programs offer an auto-correction mechanism as git does. But nowadays the frustration is over thanks to Thefuck, a utility whose colourful name well summarises the typical reaction of a shell user in front of a command failing because of a typo. It relies on a rule-based correction mechanism, and custom rules can be easily added, or existing rules can be excluded.
The application can be installed by evaluating in the shell the output of
thefuck --alias
, that defines an alias which will perform all the required
magic under the hood. Since the default value for the alias name is a type of
word that one may not always
want to leave around in the history of their machines, it is possible to set a
custom alias name, for instance fixit
:
eval "$(thefuck --alias fixit)"
Now it is possible to autocorrect a failed command by simply typing fixit
.
The application will show a prompt allowing to accept the suggested fix, scroll
through alternative suggestions, or abort:
No need to say that this utility needs to be used with caution and
never blindly, since in less trivial cases the correction may not be the one
expected and putting together sudo
and random commands is a perfect recipe
for disaster.
Zsh
While bash is the ubiquitous shell and the most sensible option for portable scripting, modern alternative shells exists, such as fish or Zsh, offering handy built-ins and a large ecosystem of plugins that make them suitable to replace bash for interactive use. Personally I opt for Zsh because of its richness of features and large plugin ecosystem, but the choice of a shell is also a matter of taste and many great alternatives exist, for which similar considerations hold.
Zsh can be set as the login shell by using the chsh (change shell) Unix tool, which is most likely present in your system. What follows is not intended as a tutorial nor an introduction to Zsh, which could easily take not just a blog post but rather a book on its own. It is instead a display of some useful features and extensions available, and for the curious reader further details are available in the respective documentations and tutorials.
Configuration
Similarly to bash, Zsh sources a series of files at startup, that can be used
to control the environment and settings. Each file can be present in system
space (inside /etc
) and user space (a dot-file inside $ZDOTDIR
, which is
~/
by default), and system files are always sourced before user files. The
first file to be sourced is zshenv
, followed by zprofile
(only for login
shells), zshrc
(only for interactive shells), and zlogin
(only for login
shells).
In a post about Zsh it is impossible to avoid mentioning Oh My
Zsh, a popular framework for Zsh
configuration and plugin management. However, handling the configuration
manually is also very easy, and many Zsh plugins may be already shipped as
packages with your Linux distribution. What follows is an example .zshrc
file
containing some useful basic settings.
# A large and neat history is valuable when using some tools
export HISTFILE=~/.zsh_history
export HISTSIZE=10000
export SAVEHIST=${HISTSIZE}
setopt inc_append_history # Write immediately to history file
setopt share_history # Share history among sessions
setopt hist_reduce_blanks # Trim whitespace
setopt hist_ignore_all_dups # Ignore duplicates
setopt hist_ignore_space # Ignore entries starting with space
bindkey -v # Enable vi-mode (use only if familiar with vi)
# Some useful keybindings
bindkey "^[[3~" delete-char
bindkey "^[[H" beginning-of-line
bindkey "^[[F" end-of-line
bindkey "^[[A" up-line-or-history
bindkey "^[[B" down-line-or-history
bindkey '^[[1;5C' emacs-forward-word
bindkey '^[[1;5D' emacs-backward-word
bindkey '^w' backward-kill-word
bindkey '^r' history-incremental-search-backward
Custom prompt
While the default shell prompt has a simple and reassuring look that remained
mostly unchanged through decades, some customisations can make it much more
informative, speeding up the workflow. Of course, Zsh allows to specify a
prompt template to be expanded, through the
PROMPT
variable. Many
themes from the community are available, and an example gallery with themes to
please all tastes can be found in the oh-my-zsh
wiki.
A theme I personally like is Spaceship, which packs many useful features in a minimalistic, elegant, and unobtrusive look. Features include automatic hostname and username display, highlight of exit status, execution time, status indicator for git, mercurial, virtual environments, and many more.
Installation is as simple as sourcing a script in .zshrc
, and a
large number of
options
can be changed by setting proper environment variables:
# The exact location may vary on your installation
. /usr/lib/spaceship-prompt/spaceship.zsh
# Customise some options
SPACESHIP_CHAR_SYMBOL='%% '
SPACESHIP_USER_SHOW=always
Completion
It is well known that the shell offers command completion via the
Tab key. Through custom completion functions, any program can
instruct the shell on how to complete its arguments. Zsh supports both its own
completion function syntax and bash
completion functions, and many completion
scripts are available.
The completion system for Zsh can be initialised through a guided wizard by
calling autoload -Uz compinstall && compinstall
, which will write relevant
code to ~/.zshrc
, or manually by inserting relevant commands in the
~/.zshrc
, similarly to the following:
# Initialise the completion system
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
# Show completion menu if at least two options are available
zstyle ':completion:*' menu select=2
Last but not least, fzf offers a context-aware fuzzy completion
mechanism
for command arguments, that can be used by inserting a trigger sequence (by
default a double asterisk, **
) followed by a Tab pressure. For
instance, to fuzzy-search a file from a given directory, to be passed as a
positional argument to vim:
Autosuggestions
While command completion is a great feature, command prediction can be an even better feature. A solution in this sense is offered by zsh-autosuggestions, which proposes a completion for the commands as you type, based on the shell history.
Autosuggestions are showed as greyed text, and can be accepted entirely by
pressing End or →, or one word at a time by pressing a
key combination bound to forward-word
(ctrl +
→ if using the example configuration from above).
Also in this case, installation is as simple as sourcing a script in .zshrc
and, optionally, choosing a shortcut to accept the suggestions or configuring
other
settings:
# The exact location may vary on your installation
. /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
bindkey '^ ' autosuggest-accept
Autojump
When moving around in the filesystem from the command line, having to type exact paths can be rather annoying, and even autocompletion helps only up to a certain extent. But also in this case, predicting can be faster than completing thanks to autojump, a tool that keeps track of visited directories, and allows to jump to a destination by specifying a part of the name (opting, in case of ambiguity, for the most visited among the matching alternatives).
Autojump is implemented in Python, and its functionalities are conveniently exposed through a set of wrapping shell functions, available after sourcing its install script:
# The exact location may vary on your installation
. /etc/profile.d/autojump.zsh
The syntax is unsurprisingly concise, given that saving keystrokes is the exact
purpose of the tool. A function named j
allows to jump to a directory, and
its sisters jc
and jo
allow to jump to a child directory and to open a
directory in the file manager respectively.
Syntax highlighting
Syntax highlighting is an essential helper when programming, so why should the terminal shell be an exception? The zsh-syntax-highlighting plugin adds colour to the command line. Besides usual syntax highlighting features, such as literal and variable highlight, it provides a very useful visual feedback by highlighting valid commands in green, and invalid ones in red, and it underlines valid file names and patterns.
To enable syntax highlighting, it is enough to source its installation script.
This should be done at the end of the .zshrc
file, otherwise widgets defined
after it will not update the syntax highlighting.
# The exact location may vary on your installation
. /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
Alias tips
Aliasing is a well-known shell feature that allows to define shorter or easier to memorise shortcuts for commands, and it can be a valid ally to save keystrokes in the everyday routine. When making abundant use of aliasing, it may happen to quickly forget the less used ones, or it may take some time to memorise the new ones. The alias-tips plugin overcomes this annoyance by showing a friendly reminder every time a full-length shell or git command is used while there is an alias defined for it.
Also in this case, installation is a matter of sourcing the script from
.zshrc
:
# The exact location may vary on your installation
. /usr/share/zsh/plugins/alias-tips/alias-tips.plugin.zsh