Question

Handling STDIN and Command-Line Arguments in Ruby

rubystdin

Question

How should a Ruby command-line program accept input from either standard input or command-line arguments?

It should support uses such as:

cat input.txt | ruby myprog.rb
ruby myprog.rb < input.txt
ruby myprog.rb arg1 arg2 arg3

What is a clean way to handle empty standard input as well?

#!/usr/bin/env ruby

STDIN.read.split("\n").each do |line|
  puts line
end

ARGV.each do |argument|
  puts argument
end

Short Answer

Ruby command-line programs receive streamed input through STDIN and positional command-line values through ARGV. You will learn when to read each source, how empty input behaves, and how to choose a predictable input policy for a script.

Concept

STDIN (standard input) is an input stream supplied by the shell. A program can receive it from a pipe (|), a redirected file (<), or an interactive terminal.

ARGV is an array containing the command-line arguments after the program name.

ruby myprog.rb one two

In this command, Ruby starts with:

ARGV # => ["one", "two"]

These are separate sources with different purposes:

  • Use STDIN for potentially large or line-oriented data, especially from pipes and redirected files.
  • Use ARGV for options, filenames, identifiers, and small explicit values.

An empty redirected file is not an error by itself. Reading it returns an empty string, and line iteration simply performs zero iterations. The important distinction is that an interactive terminal is an open stream: reading all of STDIN will wait until end-of-file is sent.

Mental Model

Think of ARGV as labels written on the delivery order before a program starts. They are all available immediately.

Think of STDIN as a conveyor belt. Data may arrive from another command, a file, or a person typing. You can process items as they arrive, but if you ask for the entire conveyor belt with read, Ruby must wait until the belt is finished.

An empty file is a finished conveyor belt with no items. A terminal is a conveyor belt that remains open until the user signals that they are done.

Take Quiz

Syntax and Examples

Read all standard input when the input is expected to be small:

text = STDIN.read
puts "No input supplied" if text.empty?

Read standard input one line at a time for line-based data:

STDIN.each_line do |line|
  puts line.chomp.upcase
end

chomp removes the trailing line ending without removing other meaningful whitespace.

Read command-line arguments:

ARGV.each do |argument|
  puts argument
end

A simple policy is to use arguments when they are present and otherwise read lines from standard input:

if ARGV.any?
  ARGV.each { |argument| puts argument }
else
  STDIN.each_line { |line| puts line.chomp }
end

This policy is easy to document: ruby myprog.rb red blue uses arguments, while cat colors.txt | ruby myprog.rb uses standard input.

Step by Step Execution

Consider this program:

if ARGV.any?
  input_items = ARGV
else
  input_items = STDIN.each_line.map(&:chomp)
end

input_items.each do |item|
  puts "Item: #{item}"
end

Run it with arguments:

ruby myprog.rb apple pear

Execution:

  1. ARGV is ["apple", "pear"].
  2. ARGV.any? is true.
  3. input_items becomes the argument array.
  4. The loop prints Item: apple and Item: pear.

Run it with an empty file:

ruby myprog.rb < empty.txt

Execution:

  1. ARGV is empty, so Ruby chooses STDIN.
  2. STDIN.each_line finds no lines.
  3. map produces [].
  4. The final loop prints nothing.

Run it interactively with no arguments:

ruby myprog.rb

The program waits for lines. Finish input with EOF: typically Ctrl-D on macOS/Linux and Ctrl-Z, then Enter, on Windows terminals.

Real World Use Cases

  • Unix pipelines: Receive log lines from grep, curl, or another program and transform them.
  • File redirection: Process a report with ruby summarize.rb < report.txt without making the filename part of the program interface.
  • Batch identifiers: Accept a few IDs directly, such as ruby delete_users.rb 12 15 18.
  • Text filters: Build programs that read one line at a time, normalize text, and print results for the next command in a pipeline.
  • Automation scripts: Accept arguments for configuration while receiving records through standard input, such as ruby import.rb --dry-run < users.csv.

For more complex command-line interfaces, option parsers can separate flags such as --verbose from positional input values.

Take Quiz

Real Codebase Usage

Real command-line tools usually define a clear contract instead of trying to guess whether a user "meant" to provide input.

A common pattern is:

  1. Parse options and retain positional arguments in ARGV.
  2. If positional values exist, process them.
  3. Otherwise, read STDIN line by line.
  4. Validate input early and print errors to STDERR.
abort "Provide at least one name or pipe names into the program." if ARGV.empty? && STDIN.tty?

items = ARGV.any? ? ARGV : STDIN.each_line.map(&:chomp)
items.each { |item| puts item }

STDIN.tty? is useful here: it tells you whether standard input is connected to an interactive terminal. If there are no arguments and input is a terminal, the program can show help instead of unexpectedly waiting for input.

For large input, avoid STDIN.read and STDIN.readlines, because both load all data into memory. Prefer streaming:

STDIN.each_line do |line|
  process(line.chomp)
end

Use $stderr.puts or warn for diagnostics so normal output on STDOUT remains usable in pipelines.

Common Mistakes

  • Calling STDIN.read with no redirected or piped input

    text = STDIN.read
    

    This waits for EOF when run from a terminal. Use STDIN.tty? to show usage when interactive input is not intended, or document that the user must finish typing with EOF.

  • Using split("\n") when preserving blank lines matters

    STDIN.read.split("\n")
    

    split is not the best abstraction for streaming, and its handling of trailing empty fields can be surprising. Prefer each_line for line-oriented input. If you need all lines including their exact endings, use STDIN.each_line without chomp.

  • Loading a huge stream into memory

    lines = STDIN.readlines
    

    This may be expensive for large files. Process each line as it arrives with STDIN.each_line.

  • Assuming blank standard input can always be detected before reading

    A terminal may receive input later, so there is no general, portable way to treat it as "blank" without deciding how long to wait. STDIN.tty? identifies a terminal; it does not prove that no one will type.

  • Mixing arguments and standard input without a documented rule

    If a script always reads both, users may accidentally cause it to block or process duplicate data. Decide whether arguments override STDIN, both are combined, or a flag selects the source.

  • Printing error messages to standard output

    Output from puts may be piped into another command. Send usage and errors to standard error instead:

    warn "No input provided"
    

Comparisons

ApproachBest forMain trade-off
STDIN.readSmall complete documentsWaits for EOF and uses memory proportional to all input
STDIN.each_lineLogs, CSV-like text, large filesNaturally works one line at a time rather than as one string
STDIN.readlinesSmall files when an array is truly neededReads every line into memory
ARGVFlags, filenames, IDs, small explicit valuesNot suitable for large streamed content
STDIN.tty?Choosing interactive behavior or showing usageDetects a terminal, not whether data will arrive

gets is another option for interactive programs. It reads one line and returns nil at EOF:

while (line = STDIN.gets)
  puts line.chomp
end

For ordinary line-by-line stream processing, STDIN.each_line is often clearer.

Take Quiz

Cheat Sheet

# All input as one string; empty stream => ""
text = STDIN.read

# Stream lines; empty stream => block runs zero times
STDIN.each_line do |line|
  puts line.chomp
end

# Command-line arguments
p ARGV

# Choose arguments first, otherwise standard input
items = ARGV.any? ? ARGV : STDIN.each_line.map(&:chomp)

# Is standard input an interactive terminal?
p STDIN.tty?

# Send a diagnostic to standard error
warn "Usage: ruby myprog.rb VALUE..."
  • ARGV does not include the Ruby executable or script filename.
  • Empty redirected input is normal: STDIN.read returns ""; each_line has zero lines.
  • read waits for EOF, so it can appear to hang when standard input is a terminal.
  • Prefer each_line for large, line-oriented input.
  • Use chomp to remove a record separator, usually the ending newline.

FAQ

How do I read from STDIN in Ruby?

Use STDIN.read for the complete stream or STDIN.each_line to process one line at a time.

What does Ruby return for empty STDIN?

STDIN.read returns an empty string (""). STDIN.each_line executes its block zero times.

Why does STDIN.read wait forever in my Ruby script?

When input is an interactive terminal, the stream stays open. read waits until you send EOF.

How do I detect whether input comes from a terminal in Ruby?

Use STDIN.tty?. It returns true when standard input is attached to a terminal.

Should I use ARGV or STDIN in a command-line program?

Use ARGV for explicit small values and STDIN for streamed content. Define and document what happens if both are supplied.

How do I process a large input file in Ruby?

Use STDIN.each_line and process each line immediately. Avoid read and readlines unless the input is known to be small.

How do I send errors without corrupting piped output?

Use warn "message" or $stderr.puts "message". Keep normal program results on STDOUT.

Take Quiz

Mini Project

Description

Build a small Ruby text filter that accepts words either as command-line arguments or as newline-separated standard input. It demonstrates a predictable input-source policy, line streaming, empty-input handling, and diagnostic output.

Goal

Create a program that prints unique, normalized words from arguments or standard input.

Requirements

Requirement 1

Take Quiz

Keep learning

Related questions

View more