In this post, we’ll write a simple wrapper for a CLI tool to provide it with a REPL-like environment. Interested? Suspicious? Don’t know what a REPL is or where to start? Let’s dive in!

Background
If you’re already well-acquainted with REPLs, you can skip this section.
If you’re not familiar with the term, a REPL, or a read-eval-print loop is …
a simple, interactive computer programming environment that takes single user inputs (i.e., single expressions), evaluates (executes) them, and returns the result to the user
Many programming languages already provide such functionality, whether with the standard interpreter, or an additional command that is part of the development environment.
For example, python is a script interpreter, but running it without any
parameters will present you with a REPL:
$ python
Python (...version, date...)
(...compilation, environment...)
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 6
>>> b = 7
>>> a * b
42
Similarly, tools like bc (basic calculator) provide a REPL
directly, while some compilers and interpreters provide them via a separate
binary, e.g., the Glasgow Haskell Compiler (GHC) provides
ghci, while Ruby provides irb.
REPLs are very useful not just for those who are learning to use the language or the tool, but also for experienced practitioners who want to quickly try out an idea, without having to write a new file, run the compiler (with appropriate flags), examine the output, and then cleanup the experiment. A REPL frees you from all of that because it always provides a new, pristine environment, it’s easy to create and it automatically cleans everything up when you exit, and there are no traces of it left.
However, there are many tools that don’t provide a REPL of any sort, and maybe your favorite tool, or a command that you wrote yourself, doesn’t provide one either. Maybe it’s worthwhile to invest in building a complete REPL-like experience, but that’s probably a non-trivial amount of work. Is it possible to write something simple to get most of the benefits, without understanding the internals of the tool and modifying it?
Turns out, the answer is YES! Let’s go.
Minimum viable REPL
To start, we naturally need to select a tool that we want to wrap with a REPL. Ideally, this tool already has a way to execute different commands given different flags and command-line arguments as that simplifies the process, but if the only way it can work is via files, we can work with that, too! Note that as per above, the user expectation is that the user does not create or clean up any temporary files, build artifacts, error logs, etc., so if we need to do that as part of our wrapper, we have to take care to clean it up ourselves. We’ll come back to this later.
For simplicity, we’ll start by writing a wrapper for git, because it’s
something we use quite a bit (for one, since this website is versioned in Git),
and when using Git, we typically have to run many commands in sequence, e.g.:
# typical Git session after creating a new blog post
$ git checkout -b my-new-blog-post
$ git add index.md
$ git add title-image.png
$ git commit
# write commit message
$ git push
In some cases, this may also involve git diff, or git unstage, git rebase, or a host of other commands. It
gets tiring of having to type git every time before each command, so how can
we avoid that?
Let’s write a small script:
#!/bin/bash -u
#
# Copyright 2020 Misha Brukman
# SPDX-License-Identifier: Apache-2.0
# https://misha.brukman.net/blog/2020/04/add-a-repl-to-any-cli-tool/
echo "This is the Git REPL; exit via Ctrl-D."
while true; do
  echo -n "$(pwd)> git "
  read command || break
  git $command
done
# Add a blank line at the end for a clean prompt when exiting.
echo
That’s it! This works well enough for a number of basic use cases: for example,
you can now quickly go through the above command list without typing the git
prefix, while the command prompt will always remind you that you’re in
git-mode.
Adding command-line history
One thing you’ll note is that this prompt is not quite as good as your standard shell prompt: while you can use Backspace to edit your command, there’s no command history that you can cycle via ↑ and ↓; instead, they generate strange codes on the screen:
This is the Git REPL; exit via Ctrl-D.
/my/path> git ^[[A^[[B
Turns out, functionality such as this is provided by the GNU Readline library—in fact, that’s the library that’s used by Bash—so if we want the same functionality, we need to use Readline or something similar. However, Readline is a library, and we’re trying to wrap a CLI tool in a shell script, so what do we do?
Turns out, there’s a program called rlwrap which stands for
“readline wrapper” and it does exactly what we need: it wraps a CLI
tool while providing Readline functionality.
Installing rlwrap
To use rlwrap, before proceeding, let’s install it on our system:
| OS | Command | 
|---|---|
| Debian, Ubuntu | sudo apt install rlwrap | 
| RedHat, CentOS | sudo yum install rlwrap | 
| Fedora | sudo dnf install rlwrap | 
| ArchLinux | sudo pacman -S rlwrap | 
| macOS | sudo brew install rlwrap | 
Adjust accordingly for your own system.
Alternatively, if your system does not have this package provided, you can install it from source:
Start by downloading a recent release (or, if you’re feeling adventurous, download or
git clonethe latest code on themasterbranch)From the directory containing the
rlwrapsource, run the usual commands:$ ./configure && make && sudo make install
For more details and troubleshooting any issues, refer to the rlwrap
installation docs.
Using rlwrap
How does rlwrap work? The catch is that in order to wrap your CLI tool, you
have to wrap your CLI tool with rlwrap as follows:
$ rlwrap git-repl.sh
Otherwise, it doesn’t work. However, who’s going to remember to do this every time? We need to do this automatically.
Let’s update our script to automatically wrap itself with rlwrap, but to
avoid an infinite loop, we’ll need to add a marker (like an environment
variable). Here’s the new version of our script:
#!/bin/bash -u
#
# Copyright 2020 Misha Brukman
# SPDX-License-Identifier: Apache-2.0
# https://misha.brukman.net/blog/2020/04/add-a-repl-to-any-cli-tool/
if ! [ -n "${REPL_USING_RLWRAP:-}" ]; then
  # Use `rlwrap` if available.
  if which rlwrap > /dev/null 2>&1; then
    exec env REPL_USING_RLWRAP=1 rlwrap "$0" "$@"
  else
    echo 'Install `rlwrap` and re-run this script to get command history.'
  fi
fi
echo "This is the Git REPL; exit via Ctrl-D."
while true; do
  echo -n "$(pwd)> git "
  read command || break
  git $command
done
# Add a blank line at the end for a clean prompt when exiting.
echo
Note that this is a simple script which assumes you’re running it as:
$ ./git-repl-rlwrap.shIf you start it via:
$ bash git-repl-rlwrap.shit will not work. It can be extended to handle this; consider it an exercise for the reader.
Now we have a fully-functional script, with command history, and you can easily
swap out the git prompt and command for any other tool, and it will work just
fine.
But why stop there? Let’s add ability to run non-Git commands, as well as some built-ins to simplify our REPL even further.
Escape the REPL with shell commands
One thing you may have noticed while testing this script is that since we
hard-code the git prefix to all of our commands, we cannot run any other
command, such as ls or cd or our favorite $EDITOR to change a file—we
have to exit the REPL to do any of those things, which is disruptive.
Let’s add an ability to run arbitrary shell commands. The rule we’re going to
use is that if they’re prefixed with !, then it’s a shell command; otherwise,
we’ll run it through git. So to run ls -l, we’ll type !ls -l, and so on.
This is a very simple update to our script; here’s the latest version:
#!/bin/bash -u
#
# Copyright 2020 Misha Brukman
# SPDX-License-Identifier: Apache-2.0
# https://misha.brukman.net/blog/2020/04/add-a-repl-to-any-cli-tool/
if ! [ -n "${REPL_USING_RLWRAP:-}" ]; then
  # Use `rlwrap` if available.
  if which rlwrap > /dev/null 2>&1; then
    exec env REPL_USING_RLWRAP=1 rlwrap "$0" "$@"
  else
    echo 'Install `rlwrap` and re-run this script to get command history.'
  fi
fi
echo "This is the Git REPL; exit via Ctrl-D."
while true; do
  echo -n "$(pwd)> git "
  read command || break
  # A command with `!` prefix executes directly via the shell, not `git`.
  if [ "${command:0:1}" = '!' ]; then
    ${command:1}
  else
    git $command
  fi
done
# Add a blank line at the end for a clean prompt when exiting.
echo
And there you have it!
If you want to add internal settings or other functionality, consider adding it
via a : prefix on commands, which you can easily add by following the same
pattern as above.
You can extend it to run C or C++ compilers which don’t have REPLs by
outputting a line into a file, surrounded by standard #includes as well as an
int main() function declaration, and then running that through the relevant
compiler. This could be useful for testing expressions or even complex template
declarations and usage.
Now you’re equipped to build custom REPLs for any CLI tool of your choosing! Let me know what you end up using it for; I’d love to hear more real-world use cases.