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
STDINfor potentially large or line-oriented data, especially from pipes and redirected files. - Use
ARGVfor 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.
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:
ARGVis["apple", "pear"].ARGV.any?istrue.input_itemsbecomes the argument array.- The loop prints
Item: appleandItem: pear.
Run it with an empty file:
ruby myprog.rb < empty.txt
Execution:
ARGVis empty, so Ruby choosesSTDIN.STDIN.each_linefinds no lines.mapproduces[].- 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.txtwithout 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.
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:
- Parse options and retain positional arguments in
ARGV. - If positional values exist, process them.
- Otherwise, read
STDINline by line. - 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.readwith no redirected or piped inputtext = STDIN.readThis 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 mattersSTDIN.read.split("\n")splitis not the best abstraction for streaming, and its handling of trailing empty fields can be surprising. Prefereach_linefor line-oriented input. If you need all lines including their exact endings, useSTDIN.each_linewithoutchomp. -
Loading a huge stream into memory
lines = STDIN.readlinesThis 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
putsmay be piped into another command. Send usage and errors to standard error instead:warn "No input provided"
Comparisons
| Approach | Best for | Main trade-off |
|---|---|---|
STDIN.read | Small complete documents | Waits for EOF and uses memory proportional to all input |
STDIN.each_line | Logs, CSV-like text, large files | Naturally works one line at a time rather than as one string |
STDIN.readlines | Small files when an array is truly needed | Reads every line into memory |
ARGV | Flags, filenames, IDs, small explicit values | Not suitable for large streamed content |
STDIN.tty? | Choosing interactive behavior or showing usage | Detects 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.
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..."
ARGVdoes not include the Ruby executable or script filename.- Empty redirected input is normal:
STDIN.readreturns"";each_linehas zero lines. readwaits for EOF, so it can appear to hang when standard input is a terminal.- Prefer
each_linefor large, line-oriented input. - Use
chompto 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.
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
Keep learning
Related questions
Add a Default Value to an Existing Column in a Rails Migration
Learn how to add or change a default value on an existing database column using Rails Active Record migrations safely.
Calling a Class Method from an Instance in Ruby
Learn how to call a class method from an instance in Ruby using self.class, with examples, pitfalls, and practical usage patterns.
Calling an Overridden Monkey-Patched Method in Ruby
Learn how to call the original method when monkey patching in Ruby, including alias_method patterns, examples, pitfalls, and practical usage.