Skip to content

Instantly share code, notes, and snippets.

@pvdb
Last active September 6, 2023 10:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pvdb/da0d384cbf98907f507e019844de8e2d to your computer and use it in GitHub Desktop.
Save pvdb/da0d384cbf98907f507e019844de8e2d to your computer and use it in GitHub Desktop.
wrap a non-interactive command in an interactive REPL _(read-eval-print loop)_
#!/usr/bin/env ruby
# frozen_string_literal: true
REPL_NAME = 'repl'
REPL_VERSION = '1.0.0'
#
# INSTALLATION
#
# brew install rlwrap
#
# ln -s ${PWD}/repl $(brew --prefix)/bin/
# sudo ln -s ${PWD}/repl /usr/local/bin/
#
# DOCUMENTATION
#
# brew install groff
# gem install ronn
#
# ronn --roff --html --date="$(date -Idate)" --organization PVDB --manual="Awesome Utilities" repl.1.ronn
#
require 'mkmf'
require 'fileutils'
require 'shellwords'
require 'io/console'
##
# script-specific Kernel extension
module Kernel
def which(executable)
MakeMakefile.find_executable0(executable)
end
end
##
# script-specific String extensions
class String
def colorize(color_code)
"\e[#{color_code}m#{self}\e[0m"
end
def true?
self == 'true'
end
def comment?
start_with? '#'
end
end
##
# version of rlwrap utility
def rlwrap_version
`rlwrap --version`.chomp
rescue Errno::ENOENT
'rlwrap not installed'
end
##
# version of repl script
def repl_version
"#{REPL_NAME} #{REPL_VERSION} (#{rlwrap_version})"
end
if ARGV.delete('--version')
IO.console.puts repl_version
exit(0)
end
if ARGV.delete('--help') || ARGV.empty?
IO.console.puts DATA.read
exit(0)
end
if ARGV.delete('--man')
exec "man #{File.join(__dir__, 'repl.1')}"
exit(0) # superfluous, really...
end
if ARGV.delete('--html')
exec "open #{File.join(__dir__, 'repl.1.html')}"
exit(0) # superfluous, really...
end
# list of remaining options/flags
OPTIONS = %w[--stdin --printf --escape --debug --clear --quiet].freeze
# is repl running "inside" rlwrap?
def repl_wrapped?
!ENV['__REPL_RLWRAP__'].nil?
end
unless repl_wrapped?
# merge "${HOME}/.repl.rc" into ENV
if File.file? repl_rc = File.join(Dir.home, '.repl.rc')
File.readlines(repl_rc).map(&:strip).each do |line|
next if line.empty?
next if line.comment?
key, value = line.chomp.split('=')
# strip surrounding quotes
value.delete_prefix!('"')
value.delete_suffix!('"')
ENV[key] = value
end
end
# replace process with `rlwrap`-ed version
if (rlwrap_exec = which('rlwrap'))
rlwrap_args = []
# rubocop:disable Metrics/BlockNesting
unless (base_cmd = (ARGV - OPTIONS).map(&:strip).first).empty?
base_cmd = File.basename(base_cmd)
default_history_dir = Dir.home
history_dir = ENV['REPL_HISTORY_DIR'] || default_history_dir
if File.directory? history_dir = File.expand_path(history_dir)
history_file = File.join(history_dir, ".#{base_cmd}_history")
rlwrap_args += ['-H', history_file]
end
default_completion_dir = File.join(Dir.home, '.repl')
completion_dir = ENV['REPL_COMPLETION_DIR'] || default_completion_dir
if File.directory? completion_dir = File.expand_path(completion_dir)
completion_file = File.join(completion_dir, base_cmd)
rlwrap_args += ['-f', completion_file] if File.exist? completion_file
end
end
# rubocop:enable Metrics/BlockNesting
ENV['__REPL_RLWRAP__'] = Process.pid.to_s
exec("#{rlwrap_exec} #{rlwrap_args.join(' ')} #{$PROGRAM_NAME} #{ARGV.join(' ')}")
end
end
# process the options
stdin = ARGV.delete('--stdin')
printf = ARGV.delete('--printf')
escape = ARGV.delete('--escape') || ENV['REPL_ESCAPE']&.true?
debug = ARGV.delete('--debug') || ENV['REPL_DEBUG']&.true?
clear = ARGV.delete('--clear') || ENV['REPL_CLEAR']&.true?
quiet = ARGV.delete('--quiet') || ENV['REPL_QUIET']&.true?
# command is whatever's left
if (cmd_string = ARGV.join(' ').strip).empty?
IO.console.puts 'No command specified... use `--help`'
exit(0)
end
cmd_template = if stdin
pipe_cmd = which(printf ? 'printf' : 'echo')
"#{pipe_cmd} \"%s\" | #{cmd_string}"
elsif ARGV.grep(/%s/).any?
cmd_string # the '%s' is embedded
else
"#{cmd_string} %s"
end
prompt_prefix = (repl_wrapped? ? '↪' : '↳')
prompt_suffix = '>>'
default_prompt = debug ? cmd_template : cmd_string
default_prompt = default_prompt.inspect if debug
default_prompt = default_prompt.colorize(1) # bold
default_prompt = "#{prompt_prefix} #{default_prompt} #{prompt_suffix}"
repl_prompt = ENV.fetch('REPL_PROMPT', default_prompt)
loop do
# prompt user for cmd arguments
IO.console.print(repl_prompt, ' ') if $stdin.tty? || !quiet
line = begin
$stdin.gets&.strip # nil when ^D
rescue Interrupt
nil # Interrupt is raised for ^C
end
# echo input if read from stdin pipe
IO.console.puts(line) unless $stdin.tty? || quiet
break unless line # ^C / ^D / EOF
line = Shellwords.escape(line) if escape
# command = format(cmd_template, line) # expand single '%s' placeholder
command = cmd_template.gsub('%s', line) # expand _all_ '%s' placeholders
IO.console.puts "\e[H\e[2J" if clear
IO.console.puts "$ #{command}" if debug
if system(command).nil?
IO.console.print 'system("', command, '"): ' unless debug
IO.console.puts '<Errno::ENOENT>'.colorize(31) # red
end
# empty separator line
IO.console.puts
end
exit(0) # cleanly exit repl
__END__
Usage: repl [options] command ...
Options:
--version Display the repl version
--help Display this message
--man Display the man page
--html Open HTML version of man page
--stdin Pipe input to command's STDIN
--printf Avoid newline chars in STDIN
--escape Shell escape user's input
--debug Display each command executed
--clear Clear terminal after each command
--quiet Don't echo the prompt in pipelines
That's all Folks!
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "REPL" "1" "February 2023" "PVDB" "Awesome Utilities"
.
.SH "NAME"
\fBrepl\fR \- sometimes you \fIreally\fR need a repl
.
.SH "SYNOPSIS"
\fBrepl\fR \fI\fIoptions\fR\fR \fIcommand\fR <\.\.\.>
.
.SH "DESCRIPTION"
\fBrepl\fR wraps a non\-interactive \fBcommand\fR in an interactive read\-eval\-print\-loop prompt\. Each line you type into the prompt is executed as arguments to \fBcommand\fR\. Anything written to standard output or standard error by the \fBcommand\fR is displayed\.
.
.P
If you have \fBrlwrap(1)\fR installed you\'ll automatically get the full benefits of readline: history, reverse searches, etc\.
.
.P
\fBrepl\fR is meant to wrap programs which accept command line arguments and print to the standard output\. It keeps no state between executed lines and, as such, cannot be used to replace \fBirb\fR or the Python REPL (for example)\.
.
.SH "EXAMPLES"
Using \fBrepl\fR with \fBredis\-cli\fR:
.
.IP "" 4
.
.nf
$ repl redis\-cli
>> set name chris
OK
>> get name
chris
>> info
redis_version:1\.000
uptime_in_seconds:182991
uptime_in_days:2
\.\. etc \.\.
.
.fi
.
.IP "" 0
.
.P
Using \fBrepl\fR with Ruby\'s \fBgem\fR:
.
.IP "" 4
.
.nf
$ repl gem
>> \-\-version
1\.3\.5
>> search yajl
*** LOCAL GEMS ***
yajl\-ruby (0\.6\.7)
>> search yajl \-r
*** REMOTE GEMS ***
brianmario\-yajl\-ruby (0\.6\.3)
filipegiusti\-yajl\-ruby (0\.6\.4)
jdg\-yajl\-ruby (0\.5\.12)
oortle\-yajl\-ruby (0\.5\.8)
yajl\-ruby (0\.6\.7)
.
.fi
.
.IP "" 0
.
.P
Using \fBrepl\fR with \fBgit\fR:
.
.IP "" 4
.
.nf
$ repl git
>> branch
gh\-pages
* master
>> tag
rm
v0\.1\.0
v0\.1\.1
v0\.1\.2
v0\.1\.3
>> tag \-d rm
Deleted tag \'rm\'
>> pwd
git: \'pwd\' is not a git\-command\. See \'git \-\-help\'\.
Did you mean this?
add
.
.fi
.
.IP "" 0
.
.SH "OPTIONS"
.
.TP
\fB\-h\fR, \fB\-\-help\fR
Displays usage information\.
.
.TP
\fB\-\-stdin\fR
Pipe input to command\'s STDIN\.
.
.TP
\fB\-\-debug\fR
Displays debug information while running\.
.
.TP
\fB\-\-man\fR
Displays this man page\.
.
.SH "COMPLETION"
Because \fBrlwrap\fR supports completion, \fBrepl\fR does too\. Any file in \fB~/\.repl\fR matching the name of the command you start \fBrepl\fR with will be used for completion\.
.
.P
For instance, a file named \fB~/\.repl/redis\-cli\fR containing "get set info" will cause "get", "set", and "info" to be tab completeable at the \fBrepl redis\-cli\fR prompt\.
.
.P
The directory searched for completion files can be configured using the \fBREPL_COMPLETION_DIR\fR environment variable\.
.
.SH "COMMAND HISTORY"
Because \fBrlwrap\fR supports command history, \fBrepl\fR does too\. Any file in \fB~/\fR matching the name of the command you start \fBrepl\fR with prefix with a dot and suffixed with "_history" will be used for completion\.
.
.P
For instance, a file named \fB~/\.redis\-cli_history\fR containing a newline separated list of "get set info" will cause "get", "set", and "info" to be reachable using the up arrow as command history at the \fBrepl redis\-cli\fR prompt\.
.
.P
The directory searched for history files can be configured using the \fBREPL_HISTORY_DIR\fR environment variable\.
.
.SH "ENVIRONMENT"
.
.SS "REPL_PROMPT"
the prompt to display before each line of input (defaults to \fB>>\fR)
.
.SS "REPL_ESCAPE"
"shell escape" input lines before invoking command
.
.SS "REPL_DEBUG"
print out the expanded command before executing it
.
.SS "REPL_CLEAR"
clear the TTY console before running each command
.
.SS "REPL_HISTORY_DIR"
directory in which command history files are kept
.
.SS "REPL_COMPLETION_DIR"
directory in which command completion files are kept
.
.SH "HOMEPAGE"
\fIhttp://github\.com/pvdb/repl\fR
.
.P
You will find many more practical examples there!
.
.SH "BUGS"
\fIhttp://github\.com/pvdb/repl/issues\fR
.
.SH "AUTHOR"
Peter Vandenberk :: @pvdb \fIhttps://github\.com/pvdb\fR
.
.SH "CREDITS"
Chris Wanstrath :: @defunkt \fIhttps://github\.com/defunkt\fR
.
.P
Check out his (awesome, but unmaintained) original version \fIhttps://github\.com/defunkt/repl\fR on which this one is based!
.
.SH "SEE ALSO"
rlwrap(1), readline(3)
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' value='text/html;charset=utf8'>
<meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
<title>repl(1) - sometimes you &lt;em&gt;really&lt;/em&gt; need a repl</title>
<style type='text/css' media='all'>
/* style: man */
body#manpage {margin:0}
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
.mp h2 {margin:10px 0 0 0}
.mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
.mp h3 {margin:0 0 0 4ex}
.mp dt {margin:0;clear:left}
.mp dt.flush {float:left;width:8ex}
.mp dd {margin:0 0 0 9ex}
.mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
.mp pre {margin-bottom:20px}
.mp pre+h2,.mp pre+h3 {margin-top:22px}
.mp h2+pre,.mp h3+pre {margin-top:5px}
.mp img {display:block;margin:auto}
.mp h1.man-title {display:none}
.mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
.mp h2 {font-size:16px;line-height:1.25}
.mp h1 {font-size:20px;line-height:2}
.mp {text-align:justify;background:#fff}
.mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
.mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
.mp u {text-decoration:underline}
.mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
.mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
.mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
.mp b.man-ref {font-weight:normal;color:#434241}
.mp pre {padding:0 4ex}
.mp pre code {font-weight:normal;color:#434241}
.mp h2+pre,h3+pre {padding-left:0}
ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
ol.man-decor {width:100%}
ol.man-decor li.tl {text-align:left}
ol.man-decor li.tc {text-align:center;letter-spacing:4px}
ol.man-decor li.tr {text-align:right;float:right}
</style>
</head>
<!--
The following styles are deprecated and will be removed at some point:
div#man, div#man ol.man, div#man ol.head, div#man ol.man.
The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
.man-navigation should be used instead.
-->
<body id='manpage'>
<div class='mp' id='man'>
<div class='man-navigation' style='display:none'>
<a href="#NAME">NAME</a>
<a href="#SYNOPSIS">SYNOPSIS</a>
<a href="#DESCRIPTION">DESCRIPTION</a>
<a href="#EXAMPLES">EXAMPLES</a>
<a href="#OPTIONS">OPTIONS</a>
<a href="#COMPLETION">COMPLETION</a>
<a href="#COMMAND-HISTORY">COMMAND HISTORY</a>
<a href="#ENVIRONMENT">ENVIRONMENT</a>
<a href="#HOMEPAGE">HOMEPAGE</a>
<a href="#BUGS">BUGS</a>
<a href="#AUTHOR">AUTHOR</a>
<a href="#CREDITS">CREDITS</a>
<a href="#SEE-ALSO">SEE ALSO</a>
</div>
<ol class='man-decor man-head man head'>
<li class='tl'>repl(1)</li>
<li class='tc'>Awesome Utilities</li>
<li class='tr'>repl(1)</li>
</ol>
<h2 id="NAME">NAME</h2>
<p class="man-name">
<code>repl</code> - <span class="man-whatis">sometimes you <em>really</em> need a repl</span>
</p>
<h2 id="SYNOPSIS">SYNOPSIS</h2>
<p><code>repl</code> <var><a href="#OPTIONS" title="OPTIONS" data-bare-link="true">options</a></var> <var>command</var> &lt;...></p>
<h2 id="DESCRIPTION">DESCRIPTION</h2>
<p><code>repl</code> wraps a non-interactive <code>command</code> in an interactive
read-eval-print-loop prompt. Each line you type into the prompt is
executed as arguments to <code>command</code>. Anything written to standard
output or standard error by the <code>command</code> is displayed.</p>
<p>If you have <code>rlwrap(1)</code> installed you'll automatically get the full
benefits of readline: history, reverse searches, etc.</p>
<p><code>repl</code> is meant to wrap programs which accept command line arguments
and print to the standard output. It keeps no state between executed
lines and, as such, cannot be used to replace <code>irb</code> or the Python
REPL (for example).</p>
<h2 id="EXAMPLES">EXAMPLES</h2>
<p>Using <code>repl</code> with <code>redis-cli</code>:</p>
<pre><code>$ repl redis-cli
&gt;&gt; set name chris
OK
&gt;&gt; get name
chris
&gt;&gt; info
redis_version:1.000
uptime_in_seconds:182991
uptime_in_days:2
.. etc ..
</code></pre>
<p>Using <code>repl</code> with Ruby's <code>gem</code>:</p>
<pre><code>$ repl gem
&gt;&gt; --version
1.3.5
&gt;&gt; search yajl
*** LOCAL GEMS ***
yajl-ruby (0.6.7)
&gt;&gt; search yajl -r
*** REMOTE GEMS ***
brianmario-yajl-ruby (0.6.3)
filipegiusti-yajl-ruby (0.6.4)
jdg-yajl-ruby (0.5.12)
oortle-yajl-ruby (0.5.8)
yajl-ruby (0.6.7)
</code></pre>
<p>Using <code>repl</code> with <code>git</code>:</p>
<pre><code>$ repl git
&gt;&gt; branch
gh-pages
* master
&gt;&gt; tag
rm
v0.1.0
v0.1.1
v0.1.2
v0.1.3
&gt;&gt; tag -d rm
Deleted tag 'rm'
&gt;&gt; pwd
git: 'pwd' is not a git-command. See 'git --help'.
Did you mean this?
add
</code></pre>
<h2 id="OPTIONS">OPTIONS</h2>
<dl>
<dt><code>-h</code>, <code>--help</code></dt><dd><p>Displays usage information.</p></dd>
<dt class="flush"><code>--stdin</code></dt><dd><p>Pipe input to command's STDIN.</p></dd>
<dt class="flush"><code>--debug</code></dt><dd><p>Displays debug information while running.</p></dd>
<dt class="flush"><code>--man</code></dt><dd><p>Displays this man page.</p></dd>
</dl>
<h2 id="COMPLETION">COMPLETION</h2>
<p>Because <code>rlwrap</code> supports completion, <code>repl</code> does too. Any file in
<code>~/.repl</code> matching the name of the command you start <code>repl</code> with will
be used for completion.</p>
<p>For instance, a file named <code>~/.repl/redis-cli</code> containing "get set
info" will cause "get", "set", and "info" to be tab completeable at
the <code>repl redis-cli</code> prompt.</p>
<p>The directory searched for completion files can be configured using
the <code>REPL_COMPLETION_DIR</code> environment variable.</p>
<h2 id="COMMAND-HISTORY">COMMAND HISTORY</h2>
<p>Because <code>rlwrap</code> supports command history, <code>repl</code> does too. Any file in
<code>~/</code> matching the name of the command you start <code>repl</code> with prefix
with a dot and suffixed with "_history" will be used for completion.</p>
<p>For instance, a file named <code>~/.redis-cli_history</code> containing a newline
separated list of "get set info" will cause "get", "set", and "info"
to be reachable using the up arrow as command history at the <code>repl
redis-cli</code> prompt.</p>
<p>The directory searched for history files can be configured using the
<code>REPL_HISTORY_DIR</code> environment variable.</p>
<h2 id="ENVIRONMENT">ENVIRONMENT</h2>
<h3 id="REPL_PROMPT">REPL_PROMPT</h3>
<p>the prompt to display before each line of input (defaults to <code>&gt;&gt;</code>)</p>
<h3 id="REPL_ESCAPE">REPL_ESCAPE</h3>
<p>"shell escape" input lines before invoking command</p>
<h3 id="REPL_DEBUG">REPL_DEBUG</h3>
<p>print out the expanded command before executing it</p>
<h3 id="REPL_CLEAR">REPL_CLEAR</h3>
<p>clear the TTY console before running each command</p>
<h3 id="REPL_HISTORY_DIR">REPL_HISTORY_DIR</h3>
<p>directory in which command history files are kept</p>
<h3 id="REPL_COMPLETION_DIR">REPL_COMPLETION_DIR</h3>
<p>directory in which command completion files are kept</p>
<h2 id="HOMEPAGE">HOMEPAGE</h2>
<p><a href="http://github.com/pvdb/repl" data-bare-link="true">http://github.com/pvdb/repl</a></p>
<p>You will find many more practical examples there!</p>
<h2 id="BUGS">BUGS</h2>
<p><a href="http://github.com/pvdb/repl/issues" data-bare-link="true">http://github.com/pvdb/repl/issues</a></p>
<h2 id="AUTHOR">AUTHOR</h2>
<p>Peter Vandenberk :: <a href="https://github.com/pvdb">@pvdb</a></p>
<h2 id="CREDITS">CREDITS</h2>
<p>Chris Wanstrath :: <a href="https://github.com/defunkt">@defunkt</a></p>
<p>Check out his (awesome, but unmaintained) <a href="https://github.com/defunkt/repl">original version</a> on which this one is based!</p>
<h2 id="SEE-ALSO">SEE ALSO</h2>
<p><span class="man-ref">rlwrap<span class="s">(1)</span></span>, <span class="man-ref">readline<span class="s">(3)</span></span></p>
<ol class='man-decor man-foot man foot'>
<li class='tl'>PVDB</li>
<li class='tc'>February 2023</li>
<li class='tr'>repl(1)</li>
</ol>
</div>
</body>
</html>

repl(1) - sometimes you really need a repl

SYNOPSIS

repl <[options]> <...>

DESCRIPTION

repl wraps a non-interactive command in an interactive read-eval-print-loop prompt. Each line you type into the prompt is executed as arguments to command. Anything written to standard output or standard error by the command is displayed.

If you have rlwrap(1) installed you'll automatically get the full benefits of readline: history, reverse searches, etc.

repl is meant to wrap programs which accept command line arguments and print to the standard output. It keeps no state between executed lines and, as such, cannot be used to replace irb or the Python REPL (for example).

EXAMPLES

Using repl with redis-cli:

$ repl redis-cli
>> set name chris
OK
>> get name
chris
>> info
redis_version:1.000
uptime_in_seconds:182991
uptime_in_days:2
.. etc ..

Using repl with Ruby's gem:

$ repl gem
>> --version
1.3.5
>> search yajl

*** LOCAL GEMS ***

yajl-ruby (0.6.7)
>> search yajl -r

*** REMOTE GEMS ***

brianmario-yajl-ruby (0.6.3)
filipegiusti-yajl-ruby (0.6.4)
jdg-yajl-ruby (0.5.12)
oortle-yajl-ruby (0.5.8)
yajl-ruby (0.6.7)

Using repl with git:

$ repl git
>> branch
  gh-pages
* master
>> tag
rm
v0.1.0
v0.1.1
v0.1.2
v0.1.3
>> tag -d rm
Deleted tag 'rm'
>> pwd
git: 'pwd' is not a git-command. See 'git --help'.

Did you mean this?
  add

OPTIONS

  • -h, --help: Displays usage information.

  • --stdin: Pipe input to command's STDIN.

  • --debug: Displays debug information while running.

  • --man: Displays this man page.

COMPLETION

Because rlwrap supports completion, repl does too. Any file in ~/.repl matching the name of the command you start repl with will be used for completion.

For instance, a file named ~/.repl/redis-cli containing "get set info" will cause "get", "set", and "info" to be tab completeable at the repl redis-cli prompt.

The directory searched for completion files can be configured using the REPL_COMPLETION_DIR environment variable.

COMMAND HISTORY

Because rlwrap supports command history, repl does too. Any file in ~/ matching the name of the command you start repl with prefix with a dot and suffixed with "_history" will be used for completion.

For instance, a file named ~/.redis-cli_history containing a newline separated list of "get set info" will cause "get", "set", and "info" to be reachable using the up arrow as command history at the repl redis-cli prompt.

The directory searched for history files can be configured using the REPL_HISTORY_DIR environment variable.

ENVIRONMENT

REPL_PROMPT

the prompt to display before each line of input (defaults to >>)

REPL_ESCAPE

"shell escape" input lines before invoking command

REPL_DEBUG

print out the expanded command before executing it

REPL_CLEAR

clear the TTY console before running each command

REPL_HISTORY_DIR

directory in which command history files are kept

REPL_COMPLETION_DIR

directory in which command completion files are kept

HOMEPAGE

http://github.com/pvdb/repl

You will find many more practical examples there!

BUGS

http://github.com/pvdb/repl/issues

AUTHOR

Peter Vandenberk :: @pvdb

CREDITS

Chris Wanstrath :: @defunkt

Check out his (awesome, but unmaintained) original version on which this one is based!

SEE ALSO

rlwrap(1), readline(3)

repl(1) -- sometimes you really need a REPL

repl wraps a non-interactive command in an interactive REPL (read-eval-print loop).

Synopsis

repl [options] command <...>

command is executed using the lines you type into repl's prompt as either command-line arguments or else as standard input, and anything written by command to standard output and standard error is displayed.

This is repeated until you exit out of repl's interactive loop by using either CTRL-C or CTRL-D at the prompt (default is >> ):

$ repl git ls-files
>> exe
exe/repl

>> lib
lib/repl.rb
lib/repl/version.rb

>> bin
bin/console
bin/setup

>> ^D
$ _

It can be used with commands you wish had an interactive mode, but don't, like gem:

$ repl gem
>> --version
3.0.3

>> sources
*** CURRENT SOURCES ***

https://rubygems.org/

>> search '^repl$'

*** REMOTE GEMS ***

repl (1.0.0)

>> ^D
$ _

Or system utilities like host:

$ repl host -t A
>> google.com
google.com has address 216.58.212.206

>> google.co.uk
google.co.uk has address 172.217.169.35

>> ^D
$ _

Usage

repl is meant to wrap programs which accept and process command line arguments or which read from and process standard input, and which in turn print to standard output or standard error, but which don't have an interactive REPL of their own.

If you have rlwrap(1) installed you'll automatically get the full benefits of readline: persistent history, reverse history searches, command-specific tab completions, rich command-line editing, etc.

Combined with rlwrap, repl can provide a much richer interactive environment even when wrapping commands that have their own basic one, by supporting readline features the command itself doesn't.

Limitation

repl keeps no state between subsequent command invocations and, as such, cannot be used to replace things like the Ruby and Python REPLs (irb, pry, etc.), or other language shells.

Credits

This version borrows very, very heavily from but is a ground-up rewrite of Chris Wanstrath's awesome original version.

Installation

repl is easily installed as a standalone script somewhere in your ${PATH}; the default install location is /usr/local/bin, but it can be anything on your ${PATH}.

Run the following commands in your preferred shell:

REPL_INSTALL_DIR=/usr/local/bin
curl -s https://gist.githubusercontent.com/pvdb/da0d384cbf98907f507e019844de8e2d/raw/repl -o "${REPL_INSTALL_DIR}/repl"
chmod 755 "${REPL_INSTALL_DIR}/repl"

This way you can run repl without any changes to your system's $PATH. Depending on your file system permissions, you may have to use sudo curl instead of just curl.

Examples

In order to show what's going on, the following examples use the --debug option, which makes repl print the expanded command line - prefixed with $ - just before executing in.

basic

By default, anything you enter on repl's prompt is passed on to the command as positional command line arguments:

$ repl --debug git
>> version
$ git version
git version 2.28.0

>> status
$ git status
fatal: not a git repository (or any of the parent directories): .git

>> ^D
$ _

You can also enter multiple command line arguments at once:

$ repl --debug git config --global
>> --get user.name
$ git config --global --get user.name
Peter Vandenberk

>> --get user.email
$ git config --global --get user.email
pvandenberk@mac.com

>> ^D
$ _

placeholders

You can control where repl inserts what you enter on the prompt by using a %s placeholder, similar to printf(3):

$ repl --debug grep %s ~/.gitconfig
>> name
$ grep name ~/.gitconfig
        name = Peter Vandenberk

>> email
$ grep email ~/.gitconfig
        email = pvandenberk@mac.com

>> ^D
$ _

You can also use a %s placeholder for the command itself, as opposed to its arguments:

$ repl --debug %s /tmp
>> file
$ file /tmp
/tmp: sticky, directory

>> ls -ld
$ ls -ld /tmp
lrwxr-xr-x@ 1 root  admin  11 15 Jun 20:19 /tmp -> private/tmp

>> ^D
$ _

Multiple %s placeholders are also supported:

$ repl --debug file /etc/%s /etc/%s_Apple_Terminal
>> bashrc
$ file /etc/bashrc /etc/bashrc_Apple_Terminal
/etc/bashrc:                ASCII text
/etc/bashrc_Apple_Terminal: ASCII text

>> zshrc
$ file /etc/zshrc /etc/zshrc_Apple_Terminal
/etc/zshrc:                ASCII text
/etc/zshrc_Apple_Terminal: ASCII text

>> ^D
$ _

standard input

Using the --stdin option tells repl to write what you enter on the prompt to the command's standard input (instead of providing it as command arguments):

$ repl --debug --stdin bc
>> 21 * 2
$ /bin/echo "21 * 2" | bc
42

>> 14 * 3
$ /bin/echo "14 * 3" | bc
42

>> ^D
$ _

Use the --printf option (in addition to --stdin) to make repl use printf(1) instead of echo(1), thus suppressing newline characters if and when these are superfluous:

$ repl --debug --stdin --printf wc -c
>> one
$ /usr/bin/printf "one" | wc -c
       3

>> two
$ /usr/bin/printf "two" | wc -c
       3

>> three
$ /usr/bin/printf "three" | wc -c
       5

>> ^D
$ _

pipelines

By "double quoting" the command, you can wrap repl around entire command pipelines:

$ repl --debug "'%s blegga|wc -c'"
>> echo
$ echo blegga|wc -c
       7

>> printf
$ printf blegga|wc -c
       6

>> ^D
$ _

escaping

In order to create these pipelines, repl doesn't by default escape shell constructs, which can cause issues:

$ repl --debug echo
>> foo|bar
$ echo foo|bar
sh: bar: command not found

>> <blegga>
$ echo <blegga>
sh: -c: line 0: syntax error near unexpected token `newline'
sh: -c: line 0: `echo <blegga>'

>> ^D
$ _

By using the --escape option, repl can be made to escape shell constructs in its input:

$ repl --debug --escape echo
>> foo|bar
$ echo foo\|bar
foo|bar

>> <blegga>
$ echo \<blegga\>
<blegga>

>> ^D
$ _

Features

Features of and improvements in this new version of repl:

new options

  1. --version - print the repl version info
    • example: repl --version
  2. --html - open the repl man page in web browser
    • example: repl --html
  3. --clear - clear terminal before the next eval
    • example: repl --clear echo
  4. --escape - shell escape user's input
    • example: compare repl echo <<<'Peter "The Rock" V.' with repl --escape echo <<<'Peter "The Rock" V.'
  5. --printf - causes --stdin to use /usr/bin/printf instead of /bin/echo to avoid adding superfluous trailing newline characters
    • example: compare repl --debug --stdin wc -c <<<"blegga" with repl --debug --stdin --printf wc -c <<<"blegga"

improved options

  1. --debug - now works correctly in conjunction with --stdin
    • example: repl --stdin --debug wc -c using defunkt/repl doesn't work

rlwrap improvements

  1. explicitly ignore options/flags for calculating history and completion files
  2. use MakeMakefile.find_executable0() instead of which(1) to find rlwrap
  3. set __REPL_RLWRAP__ to the PID of the parent repl process, instead of 0

command processing

  1. all prompts, debug and error messages are written to IO.console (not STDOUT) for pipelining purposes
    • example: repl --debug echo > blegga; cat blegga
  2. support for multiple embedded %s placeholders in the command string, not just one
    • example: repl diff source_dir/%s target_dir/%s

Configuration

The following environment variables can be used to override repl's defaults:

  • REPL_PROMPT: prompt to use in repl's read-eval-print loop (default: >>)
  • REPL_ESCAPE: equivalent to the --escape option if set to true (default: false)
  • REPL_DEBUG : equivalent to the --debug option if set to true (default: false)
  • REPL_CLEAR : equivalent to the --clear option if set to true (default: false)
  • REPL_HISTORY_DIR: directory in which history files are kept (default: ${HOME})
  • REPL_COMPLETION_DIR: directory in which completion files are kept (default: ${HOME}/.repl)

These options can also be set permantently in ${HOME}/.repl.rc, instead of "polluting" your shell environment with them; use the repl.rc template file as a starting point.

TODO

Potential improvements to this new version:

  1. add README.md instructions for "Standalone Installation"
  2. update and improve the usage instructions and examples
  3. update and improve the man page, incl. rake support
  4. make --escape the default, and/or add --no-escape
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment