rubyguides

Readline

Readline.readline(prompt = nil)

The Readline module provides command-line editing capabilities for interactive programs, similar to bash or IRB. It enables features like arrow key navigation, history, and Emacs/vi editing modes.

Basic Usage

require 'readline'

# Simple prompt
input = Readline.readline("> ")
puts "You entered: #{input}"

The basic prompt shows the minimal Readline setup: require the library, call readline with a prompt string, and work with the returned string. This is already a step up from bare gets because the user can move the cursor, edit the line, and use keyboard shortcuts before pressing Enter. The prompt string is optional, but providing one makes the interface feel more intentional and helps the user understand what is expected.

Interactive Input

require 'readline'

# Complete line editing
while line = Readline.readline("Enter command: ", true)
  break if line.nil? || line.downcase == 'exit'
  puts "Processing: #{line}"
end

The interactive input loop reads lines until the user types “exit” or sends EOF. The second argument to readline controls whether the input is added to the history buffer, which matters for navigation keys like the up arrow. When that argument is true, each accepted line becomes available for recall during the same session.

With History

require 'readline'

# Enable history
Readline::HISTORY.push("first command")
Readline::HISTORY.push("second command")

# Read with history (second param true)
line = Readline.readline("> ", true)
puts line
puts "History: #{Readline::HISTORY.to_a}"

The history example shows how to preload Readline::HISTORY with entries and then read input with the second argument set to true. That boolean controls whether the input is automatically added to the history, which saves you from calling Readline::HISTORY.push manually after every read. Preloading history is useful for tools that want to restore the user’s previous session on startup.

Completion

require 'readline'

# Basic completion
words = %w[apple banana cherry date elderberry]
Readline.completion_append_character = " "

completer = lambda do |str|
  words.grep(/^#{Regexp.escape(str)}/)
end

Readline.completion_proc = completer

input = Readline.readline("Fruit: ")

The basic completion example uses a lambda that filters a word list with a regex, which is the simplest way to get started with tab completion. Setting completion_append_character to a space means the user can start typing the next word immediately after accepting a completion, which is the behavior most people expect from shell-style tab completion in interactive tools.

Complex Completion

require 'readline'

# File completion
Readline.completion_proc = lambda do |str|
  Dir.glob("#{str}*").map { |f| f + (File.directory?(f) ? "/" : " ") }
end

The file-completion example uses Dir.glob to match paths against the current input, which is the same approach that shells use for tab completion. Adding a trailing slash to directories helps the user see at a glance whether the match is a file or a folder. This small touch makes the completion feel more like a real terminal and less like a generic autocomplete widget.

Configuration

# Edit mode (Emacs or Vi)
Readline.emacs_editing_mode      # Default
Readline.vi_editing_mode         # Vi-style

# Configure
Readline.basic_word_break_characters = " \t\n\"\\'`><=|&{"
Readline.completion_append_character = " "

The configuration section lets you switch between Emacs and Vi editing modes, which is important for users who are already comfortable with one of those keybinding styles. Setting word break characters controls where the cursor stops during word-level navigation, and the completion append character determines what is added after a tab completion is accepted.

Practical CLI Application

#!/usr/bin/env ruby
require 'readline'

COMMANDS = %w[help status start stop restart]

def prompt
  Readline.readline("> ", true) do |line|
    Readline::HISTORY.push(line)
    process_command(line)
  end
end

def process_command(line)
  cmd = line.strip.split.first
  case cmd
  when 'help'
    puts "Available: #{COMMANDS.join(', ')}"
  when 'status'
    puts "Running"
  else
    puts "Unknown command: #{cmd}"
  end
end

prompt until Readline::HISTORY.empty? || false

This CLI application skeleton shows a typical Readline loop: prompt, push to history, process the command, and repeat. The history is managed inside the block passed to readline, which runs after each line is accepted. That keeps the history logic close to the input handling, so the main loop stays focused on routing commands rather than managing state.

Readline Extensions

# Using rb-readline for cross-platform
# gem install rb-readline

require 'rbreadline'
# Same API, works on Windows

The rb-readline gem provides the same Readline API on platforms where the native extension is not available, such as Windows. Because the API surface is identical, you can write your interactive code once and swap the backend by changing a single require statement. This makes Readline-based tools more portable without forcing you to maintain two separate input paths.

Return Values

# Returns string, or nil on EOF (Ctrl+D)
line = Readline.readline("> ")
if line
  puts "Got: #{line}"
else
  puts "Goodbye!"
end

The Readline module is essential for building polished interactive command-line applications in Ruby. Once you have history, completion, and line editing working together, the REPL feels more like a real shell and less like a script that asks one question at a time. That small shift in user experience makes the tool feel professional even when the underlying logic is simple.

Working comfortably with interactive input

Readline is most valuable when the program needs to feel responsive instead of raw. It gives you history, cursor movement, and completion, but the surrounding code still needs to handle end-of-file, empty input, and commands that should exit cleanly. Keep the prompt short, strip or normalize the line before parsing, and treat nil as a normal signal that the user is done. That makes console tools feel predictable and reduces special cases in the rest of the program.

It also helps to decide early how much of the user’s input should be preserved. Some tools want raw text, while others should trim, downcase, or split the line before they act on it. When the command set grows, completion and history become more helpful because they reduce repeated typing and make the console feel steady. The method shines most when the program behaves like a tiny shell rather than a single-question script.

Keeping the interaction compact also makes failure states easier to handle. A missing command, a blank response, or an unexpected EOF can all be handled in one place instead of being scattered through the app. That keeps the loop easy to test and makes it simpler to reuse the same command parser in other entry points later on.

That same shape also makes future changes easier. If you later add aliases, help text, or command history rules, the interactive loop stays in one place and the new behavior can sit beside it. The prompt remains simple, and the command handling can grow without turning the main loop into a tangle of special cases.

See Also