2026-04-27
Some programs have interactive prompts that feel like typing into a void. No history. No cursor movement. No tab completion. You hit the up arrow and get ^[[A mocking you from the terminal. The program wasn't compiled with GNU Readline, and you're stuck.
rlwrap fixes this by wrapping any command in a Readline layer. It intercepts your keystrokes, gives you history, editing, and even programmable completion — then passes the cooked input to the underlying program. It's been around since 2004 and solves a problem you hit constantly but never think to fix.
Install it:
apt install rlwrap # Debian/Ubuntu
brew install rlwrap # macOS
pacman -S rlwrap # Arch
The classic use case is nc (netcat). Try having a conversation over a raw TCP socket without Readline — it's miserable:
# Without rlwrap: no history, no editing, arrow keys print garbage
nc localhost 8080
# With rlwrap: full line editing, up-arrow history, Ctrl-a/Ctrl-e
rlwrap nc localhost 8080
Same story with telnet, Oracle's sqlplus, Erlang's erl, various REPLs, and any homebrew interactive tool that forgot Readline exists:
rlwrap sqlplus scott/tiger@orcl
rlwrap erl
rlwrap sbcl # Common Lisp without Readline? No thanks
rlwrap ocaml
Persistent history per command. By default, rlwrap saves history to ~/.local/share/rlwrap/COMMAND_history (or ~/.COMMAND_history on older versions). Your sqlplus history survives across sessions without you doing anything. You can also set the file explicitly:
rlwrap -H ~/.my_nc_history nc localhost 9090
Tab completion from a word list. This is where it gets interesting. Feed rlwrap a file of words and it'll tab-complete them:
# Create a completions file for your custom protocol
echo -e "GET\nPOST\nDELETE\nSTATUS\nQUIT" > ~/.rlwrap/my_proto_completions
rlwrap -f ~/.rlwrap/my_proto_completions nc localhost 4444
Now you're tab-completing commands over a raw socket. Pair this with a pentest or debugging session against some obscure service and you've just built yourself a half-decent client in zero lines of code.
Filter system. rlwrap has a Perl-based filter pipeline that can transform input and output on the fly. Filters live in a shared directory and chain together:
# Colorize the output of a boring REPL
rlwrap -z pipeto cat my_repl
# Log everything to a file while adding Readline
rlwrap -z logger my_repl
Multiline editing. For REPLs where you need to paste or write multiline input, use -m to set a continuation character. Hit that character and you get dropped into your $EDITOR for a proper multiline edit, which gets pasted back in:
# Ctrl-^ opens $EDITOR for multiline input
rlwrap -m -M .sql sqlplus scott/tiger@orcl
Aliases that change your life. Put these in your shell config and forget they're there:
alias nc='rlwrap nc'
alias telnet='rlwrap telnet'
alias ocaml='rlwrap ocaml'
alias node='rlwrap node' # Node has Readline, but rlwrap's history is better
The key insight is that rlwrap works outside the target program. It doesn't care if the program is a 20-year-old binary you can't recompile, a serial console, or a network socket. It just sits between your terminal and stdin/stdout, adding civilization to the uncivilized.
One thing to watch: don't use rlwrap on programs that already handle Readline well (like bash or python3). You'll get double-handling of escape sequences and weird cursor behavior. Use it for the tools that need it — and you'll know which ones those are because they're the ones making you suffer.
