Import Readline
Toy REPL
Let us first write a tiny Python program to create a toy read-eval-print-loop (REPL) that does only one thing: add all integers entered as input into the REPL prompt. Here is the program:
while True:
try:
line = input('> ')
print(sum([int(n) for n in line.split()]))
except ValueError as e:
print('error:', e)
except (KeyboardInterrupt, EOFError):
break
Here is how it works:
$ python3 repl.py > 10 20 30 60 > 40 50 60 150 >
If we now type ↑ (the up arrow key) or ctrl+p to bring back the previous input, we see something like the following instead:
> ^[[A^[[A^P^P
It shows the keys typed literally rather than bringing up previous input like most other interactive programs with a command-line interface do. The other programs that do bring up the previous input are able to do so because they provide line editing and history capability, often with the help of a line editing and history library like GNU Readline (libreadline) or BSD Editline (libedit).
Can we have a similar line editing and history capability for our toy REPL? After all, the Python REPL itself offers such a line editing facility. Surely there must be a way to have this facility for our own programs too. Indeed there is!
Line Editing and History
To enable line editing and history in our toy REPL, we just need to
add import readline
to our program. Here is how our
program would look:
import readline
while True:
try:
line = input('> ')
print(sum([int(n) for n in line.split()]))
except ValueError as e:
print('error:', e)
except (KeyboardInterrupt, EOFError):
break
Now ↑, ctrl+p, etc. work as expected.
$ python3 repl.py > 10 20 30 60 > 40 50 60 150 > 40 50 60
The last line of input in the example above is obtained by typing
either ↑ or ctrl+p. In fact,
all of the line editing keys like ctrl+a to go
to the beginning of the line, ctrl+k to kill
the line after the cursor, etc. work as expected. The exact list of
default key-bindings supported depends on the underlying line
editing library being used by the readline
module. The
underlying library may be either the GNU Readline library or the BSD
Editline library. There are some minor differences regarding the
list of default key-bindings between these two libraries.
History File
What we have done so far achieves the goal of bringing up previous
inputs from the history. However, it does not bring back inputs
from a previous invocation of the REPL. For example, if we start
our toy REPL, enter some inputs, then quit it (say, by
typing ctrl+c), start our toy REPL again, and
type ↑ or ctrl+p, it does not
bring back the input from the previous invocation. For a full-blown
REPL meant for sophisicated usage, we may want to preserve the
history between different invocations of the REPL. This can be
achieved by using the read_history_file()
and write_history_file()
functions as shown below:
import readline
import os
HISTORY_FILE = os.path.expanduser('~/.repl_history')
if os.path.exists(HISTORY_FILE):
readline.read_history_file(HISTORY_FILE)
while True:
try:
line = input('> ')
readline.write_history_file(HISTORY_FILE)
print(sum([int(n) for n in line.split()]))
except ValueError as e:
print('error:', e)
except (KeyboardInterrupt, EOFError):
break
For more information on how to use this module, see the Python readline documentation.
Readline Wrapper
At this point, it is worth mentioning that there are many
interactive CLI tools that do not have line editing and history
capabilities. They behave like our first toy REPL example in this
post. Fortunately, there is the wonderful readline wrapper utility
known as rlwrap
that can be used to enable line editing
and history in such tools. This utility can often be easily
installed from package repositories of various operating systems.
Here is a demonstration of this tool:
$ rlwrap cat hello hello world world world
The last line of input in the example above is obtained by typing
either ↑ or ctrl+p. In the
above example, the input history is automatically saved
to ~/.cat_history
, so it is possible to bring back
inputs from a previous invocation of the command.
Obligatory Joke
Finally, an obligatory XKCD comic to conclude this post:
While the days of achieving air flight with a
single import
statement might still be a few decades
away, we do have the luxury to enable line editing and history in
our REPLs with a single such statement right now.