Overview
Ruby provides three global constants for standard I/O streams: STDIN
, STDOUT
, and STDERR
. These constants reference IO
objects that connect Ruby programs to the operating system's standard input, output, and error streams respectively.
STDIN
represents the standard input stream, typically connected to keyboard input or piped data. STDOUT
handles standard output, normally displayed in the terminal or redirected to files. STDERR
manages error output, allowing programs to separate error messages from regular output for better shell scripting and debugging.
Each constant is an instance of the IO
class, providing methods for reading from input streams and writing to output streams. Ruby also provides global methods like puts
, print
, and gets
that operate on these streams by default.
# Check the classes
STDIN.class # => IO
STDOUT.class # => IO
STDERR.class # => IO
# Default stream file descriptors
STDIN.fileno # => 0
STDOUT.fileno # => 1
STDERR.fileno # => 2
The streams handle encoding conversion automatically based on the system locale and can be configured for different character encodings. They also support various modes for reading and writing, including line-buffered and block-buffered operations.
# Stream encodings
STDOUT.external_encoding # => #<Encoding:UTF-8>
STDIN.external_encoding # => #<Encoding:UTF-8>
# Stream sync status
STDOUT.sync # => false (buffered by default)
STDERR.sync # => true (unbuffered for immediate error display)
Basic Usage
Reading from STDIN
uses methods like gets
, read
, and readlines
. The global gets
method reads from STDIN
by default, while STDIN.gets
explicitly reads from the input stream.
# Reading a single line
puts "Enter your name:"
name = gets.chomp
puts "Hello, #{name}!"
# Reading with explicit STDIN
puts "Enter age:"
age = STDIN.gets.to_i
Writing to STDOUT
and STDERR
uses methods like puts
, print
, and write
. Global output methods default to STDOUT
, while STDERR
requires explicit targeting for error messages.
# Standard output
puts "Processing data..." # Goes to STDOUT
STDOUT.puts "Explicit output" # Same as above
# Error output
STDERR.puts "Error: File not found"
warn "Warning message" # Shorthand for STDERR output
The streams support different output formatting methods. puts
adds newlines automatically, print
outputs without newlines, and write
returns the number of bytes written.
# Different output methods
puts "Line 1", "Line 2" # Two lines with newlines
print "No newline" # No automatic newline
STDOUT.write "Raw bytes\n" # Returns byte count
# Formatting output
printf "Name: %s, Age: %d\n", "Alice", 30
STDOUT.printf "Formatted: %.2f\n", 3.14159
File descriptor operations allow low-level stream manipulation. Each stream has a numeric file descriptor that the operating system uses for I/O operations.
# File descriptor operations
STDOUT.flush # Force output buffer flush
STDERR.sync = false # Enable buffering for STDERR
STDIN.close_on_exec = true # Close on exec() calls
# Stream status checks
STDIN.closed? # => false
STDOUT.tty? # => true (if connected to terminal)
STDERR.isatty # => true (alias for tty?)
Error Handling & Debugging
I/O operations can raise various exceptions that require proper handling. EOFError
occurs when attempting to read past the end of input, while IOError
handles general I/O failures.
# Handling end-of-file conditions
begin
loop do
line = STDIN.readline # Raises EOFError at EOF
puts "Read: #{line}"
end
rescue EOFError
puts "End of input reached"
end
# Safe reading with gets (returns nil at EOF)
while line = STDIN.gets
puts "Processing: #{line.chomp}"
end
Stream redirection and pipe failures require careful error handling. When STDOUT
or STDERR
streams are redirected to files or pipes, write operations can fail if the destination becomes unavailable.
# Handling write failures
begin
1000.times do |i|
STDOUT.puts "Line #{i}"
STDOUT.flush # Force write to detect pipe breaks
end
rescue Errno::EPIPE
# Pipe broken (e.g., consumer process terminated)
STDERR.puts "Output pipe closed"
exit 1
rescue Errno::ENOSPC
# No space left on device
STDERR.puts "Disk full"
exit 1
end
Encoding errors occur when input data doesn't match the expected character encoding. Ruby provides several strategies for handling invalid byte sequences.
# Handling encoding errors
STDIN.set_encoding('UTF-8', invalid: :replace, undef: :replace)
begin
content = STDIN.read
puts content
rescue Encoding::InvalidByteSequenceError => e
STDERR.puts "Encoding error: #{e.message}"
# Use binary mode for problematic input
STDIN.binmode
content = STDIN.read
end
Debugging I/O operations often requires examining stream states and buffer contents. Ruby provides methods to inspect stream configuration and status.
# Stream debugging information
def debug_stream_info(stream, name)
puts "#{name} info:"
puts " Encoding: #{stream.external_encoding}"
puts " Internal: #{stream.internal_encoding}"
puts " Sync: #{stream.sync}"
puts " Closed: #{stream.closed?}"
puts " TTY: #{stream.tty?}" if stream.respond_to?(:tty?)
puts " FD: #{stream.fileno}"
puts
end
debug_stream_info(STDIN, "STDIN")
debug_stream_info(STDOUT, "STDOUT")
debug_stream_info(STDERR, "STDERR")
Production Patterns
Command-line applications use standard streams for structured input and output processing. Separating data output on STDOUT
from status messages on STDERR
enables proper shell pipeline integration.
# Command-line data processor
class DataProcessor
def self.process_input
line_count = 0
error_count = 0
STDERR.puts "Starting data processing..."
while line = STDIN.gets
begin
processed = transform_data(line.chomp)
STDOUT.puts processed
line_count += 1
# Progress reporting to STDERR
STDERR.puts "Processed #{line_count} lines" if line_count % 1000 == 0
rescue StandardError => e
STDERR.puts "Error on line #{line_count + 1}: #{e.message}"
error_count += 1
end
end
# Final summary to STDERR
STDERR.puts "Completed: #{line_count} lines, #{error_count} errors"
exit(error_count > 0 ? 1 : 0)
end
private
def self.transform_data(line)
# Data transformation logic
line.upcase.reverse
end
end
Web applications and background services require careful stream handling for logging and output capture. Production environments often redirect streams to log files or monitoring systems.
# Production logging setup
class ProductionLogger
def initialize
# Ensure unbuffered error output
STDERR.sync = true
# Set up structured logging
setup_log_rotation
end
def log_info(message)
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
STDOUT.puts "[INFO] #{timestamp}: #{message}"
STDOUT.flush
end
def log_error(message, error = nil)
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
STDERR.puts "[ERROR] #{timestamp}: #{message}"
if error
STDERR.puts "Exception: #{error.class}: #{error.message}"
STDERR.puts error.backtrace.join("\n") if error.backtrace
end
STDERR.flush
end
private
def setup_log_rotation
# Handle SIGUSUR1 for log rotation
Signal.trap('USR1') do
STDOUT.reopen('/var/log/app/output.log', 'a')
STDERR.reopen('/var/log/app/error.log', 'a')
end
end
end
Docker and container environments require special stream handling considerations. Container orchestrators expect applications to write logs to STDOUT
and STDERR
for collection by logging drivers.
# Container-friendly application
class ContainerApp
def self.run
# JSON structured logging for container log drivers
logger = ContainerLogger.new
Signal.trap('TERM') do
logger.log(:info, "Received SIGTERM, shutting down gracefully")
shutdown_gracefully
exit 0
end
begin
main_application_loop(logger)
rescue StandardError => e
logger.log(:error, "Application crashed", error: e)
exit 1
end
end
private
def self.main_application_loop(logger)
# Main application logic with structured logging
loop do
begin
process_request(logger)
rescue StandardError => e
logger.log(:error, "Request processing failed", error: e)
end
end
end
end
class ContainerLogger
def log(level, message, **metadata)
log_entry = {
timestamp: Time.now.utc.iso8601,
level: level.to_s.upcase,
message: message
}.merge(metadata)
output_stream = level == :error ? STDERR : STDOUT
output_stream.puts JSON.generate(log_entry)
output_stream.flush
end
end
Common Pitfalls
Buffer flushing represents one of the most common sources of confusion. STDOUT
is typically line-buffered when connected to a terminal but fully buffered when redirected to files or pipes, causing unexpected delays in output appearance.
# Problem: Output appears delayed or missing
puts "Processing..."
expensive_operation # Output may not appear immediately
# Solution: Explicit flushing
puts "Processing..."
STDOUT.flush # Force immediate output
expensive_operation
# Alternative: Disable buffering entirely
STDOUT.sync = true
puts "This appears immediately"
Encoding mismatches between input data and stream configurations cause silent data corruption or encoding errors. Default encodings depend on system locale settings, creating portability issues.
# Problem: Encoding assumption failures
# Input might be Latin-1 but STDIN expects UTF-8
data = STDIN.read # May raise Encoding::InvalidByteSequenceError
# Solution: Explicit encoding handling
STDIN.set_encoding('BINARY')
binary_data = STDIN.read
utf8_data = binary_data.force_encoding('UTF-8')
# Or detect and handle encoding issues
STDIN.set_encoding('UTF-8:UTF-8', invalid: :replace, undef: :replace)
clean_data = STDIN.read
Global method confusion leads to unexpected behavior when methods like puts
and gets
don't operate on the expected streams. The warn
method writes to STDERR
, not STDOUT
, catching developers off-guard.
# Confusion: Assuming warn goes to STDOUT
warn "This goes to STDERR, not STDOUT!"
# Explicit stream targeting
STDOUT.puts "This definitely goes to STDOUT"
STDERR.puts "This definitely goes to STDERR"
# Getting input from wrong stream
# Problem: Using gets when STDIN might be redirected
input = gets # Might read from file instead of terminal
# Solution: Explicit stream or terminal detection
if STDIN.tty?
input = STDIN.gets
else
input = IO.console.gets # Always read from terminal
end
Stream redirection surprises occur when programs assume streams connect to terminals. Applications may behave differently when STDOUT
is piped or redirected, especially regarding interactive prompts and formatting.
# Problem: Interactive prompts in non-interactive environments
print "Enter password: "
password = gets.chomp # Hangs if STDIN redirected from file
# Solution: Check for terminal interaction
def get_user_input(prompt)
if STDIN.tty? && STDOUT.tty?
print prompt
STDOUT.flush
STDIN.gets.chomp
else
# Non-interactive mode
ENV['DEFAULT_INPUT'] || raise("No input available in non-interactive mode")
end
end
# Problem: ANSI colors in redirected output
puts "\e[31mError message\e[0m" # Color codes appear in files
# Solution: Conditional formatting
def colorize(text, color_code)
if STDOUT.tty?
"\e[#{color_code}m#{text}\e[0m"
else
text
end
end
puts colorize("Error message", 31)
Signal handling complications arise when programs don't properly handle stream closure during signal processing. Interrupted I/O operations can leave streams in inconsistent states.
# Problem: Interrupted I/O operations
Signal.trap('INT') do
puts "Interrupted!" # Dangerous in signal handler
exit
end
# Solution: Defer stream operations outside signal handlers
interrupted = false
Signal.trap('INT') do
interrupted = true
end
while line = STDIN.gets
break if interrupted
begin
process_line(line)
rescue StandardError => e
STDERR.puts "Error: #{e.message}"
end
end
if interrupted
STDERR.puts "Processing interrupted by user"
exit 130 # Standard exit code for SIGINT
end
Reference
Global Constants
Constant | Type | File Descriptor | Default Sync | Description |
---|---|---|---|---|
STDIN |
IO |
0 | N/A (input) | Standard input stream |
STDOUT |
IO |
1 | false |
Standard output stream |
STDERR |
IO |
2 | true |
Standard error stream |
Core IO Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#gets(separator, limit) |
separator (String), limit (Integer) |
String or nil |
Read line from stream |
#read(length, buffer) |
length (Integer), buffer (String) |
String or nil |
Read bytes from stream |
#readline(separator, limit) |
separator (String), limit (Integer) |
String |
Read line, raise EOFError at EOF |
#readlines(separator, limit) |
separator (String), limit (Integer) |
Array<String> |
Read all lines into array |
#puts(*objects) |
Variable arguments | nil |
Write objects with newlines |
#print(*objects) |
Variable arguments | nil |
Write objects without newlines |
#write(string) |
string (String) |
Integer |
Write string, return bytes written |
#printf(format, *args) |
format (String), arguments |
nil |
Write formatted string |
Stream Control Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#flush |
None | IO |
Flush output buffers |
#sync |
None | Boolean |
Get synchronous mode status |
#sync=(boolean) |
boolean (Boolean) |
Boolean |
Set synchronous mode |
#close |
None | nil |
Close stream |
#closed? |
None | Boolean |
Check if stream is closed |
#eof? |
None | Boolean |
Check for end-of-file |
#tty? |
None | Boolean |
Check if stream is terminal |
#isatty |
None | Boolean |
Alias for tty? |
Encoding Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#external_encoding |
None | Encoding |
Get external encoding |
#internal_encoding |
None | Encoding or nil |
Get internal encoding |
#set_encoding(encoding, **options) |
encoding (String/Encoding), options |
IO |
Set stream encoding |
#binmode |
None | IO |
Set binary mode |
Global Helper Methods
Method | Parameters | Returns | Description |
---|---|---|---|
gets(separator, limit) |
separator (String), limit (Integer) |
String or nil |
Read from STDIN |
puts(*objects) |
Variable arguments | nil |
Write to STDOUT |
print(*objects) |
Variable arguments | nil |
Write to STDOUT without newlines |
printf(format, *args) |
format (String), arguments |
nil |
Formatted write to STDOUT |
warn(*messages, **options) |
messages (Array), options |
nil |
Write to STDERR |
Common Exceptions
Exception | Description | Common Causes |
---|---|---|
EOFError |
End of file reached | Reading past input end with readline |
IOError |
General I/O error | Closed streams, invalid operations |
Errno::EPIPE |
Broken pipe | Writing to closed pipe/redirect |
Errno::ENOSPC |
No space left | Disk full during write |
Encoding::InvalidByteSequenceError |
Invalid byte sequence | Encoding mismatches |
Encoding::UndefinedConversionError |
Character conversion failed | Unsupported character conversion |
Stream Configuration Options
Option | Values | Default | Description |
---|---|---|---|
sync |
true , false |
STDOUT: false , STDERR: true |
Synchronous I/O mode |
encoding |
Encoding name/object | System default | Character encoding |
invalid |
:replace , :ignore |
Exception | Invalid byte handling |
undef |
:replace , :ignore |
Exception | Undefined character handling |
replace |
String | "?" |
Replacement character |
File Descriptor Constants
Constant | Value | Standard Name | Description |
---|---|---|---|
STDIN.fileno |
0 | stdin | Standard input |
STDOUT.fileno |
1 | stdout | Standard output |
STDERR.fileno |
2 | stderr | Standard error |