Running Ruby Scripts and IRB
Every Ruby program has to be run by an interpreter — there’s no separate compile step like you’d see in C or Java. Ruby gives you two main ways to execute code: saving it to a .rb file and running that file with the ruby command, or typing code directly into IRB (Interactive Ruby), Ruby’s built-in read-eval-print loop (REPL). Learning to move fluidly between the two — scripts for real programs, IRB for quick experiments — is one of the first skills every Ruby developer needs.
Overview: How Ruby Executes Your Code
When you install Ruby, you get the reference implementation, MRI (Matz’s Ruby Interpreter, sometimes called CRuby). Whenever you run ruby somefile.rb, MRI reads the file’s text, parses it into an abstract syntax tree, compiles that tree into bytecode for YARV (Yet Another Ruby VM), and then executes the bytecode from top to bottom. You never see these internal steps — from your perspective, Ruby just "runs" the file — but knowing they exist explains why ruby -c (a syntax-only check, covered below) can catch a typo without ever running your code: it stops after the parsing stage, before execution ever begins.
Top-level code in a script — statements written outside of any class, module, or method definition — isn’t running in a vacuum. Ruby executes it in the context of a special object usually called main, an instance of Object created automatically for you. That’s why you can call a method like puts directly at the top of a file without a receiver: you’re really calling it on main, and puts is available because every object inherits it, indirectly, from the Kernel module. It’s a nice illustration of Ruby’s "everything is an object" philosophy: even the untitled top level of your script has a self.
IRB works differently, even though it’s built on the same parser and evaluator. IRB is a loop: it reads one Ruby expression (waiting for more input if a block, string, or method definition isn’t finished yet), evaluates it in the context of that same kind of top-level main object, prints the inspected value the expression returned, and then loops back to read the next line. That automatic printing is the biggest practical difference from running a script: a script only prints what you explicitly tell it to with puts, print, or p, while IRB echoes the return value of every line you type, prefixed with =>. This is great for exploring the standard library or testing an idea, but it’s also a common source of confusion covered later in Common Mistakes.
Syntax
The commands you’ll use daily are simple. Here’s the general shape of each one:
| Command | What it does |
|---|---|
ruby filename.rb |
Runs the script from top to bottom and exits. |
ruby filename.rb arg1 arg2 |
Runs the script, making arg1 and arg2 available inside it via the ARGV array. |
ruby -e 'puts 1 + 1' |
Runs a short snippet of code passed directly on the command line, with no file needed. |
ruby -c filename.rb |
Checks the file’s syntax only — parses it but never executes it. |
ruby -v |
Prints the installed Ruby version and exits. |
irb |
Starts the interactive Ruby shell (REPL). |
exit (inside irb) or Ctrl+D |
Leaves the irb session and returns to your regular shell. |
#!/usr/bin/env ruby |
A "shebang", written as the very first line of a script; combined with executable permissions, it lets you run the file directly as ./filename.rb. |
Examples
Example 1: A basic script
Save the following as hello.rb and run it with ruby hello.rb.
name = "Ruby"
puts "Hello, #{name}!"
puts "This script has #{2 + 2} lines of math."
Output:
Hello, Ruby!
This script has 4 lines of math.
Ruby executes the assignment and the two puts calls in order, top to bottom. String interpolation (#{...}) evaluates the expression inside the braces — including a full arithmetic expression like 2 + 2 — and converts the result to a string before splicing it in. Nothing prints until puts is explicitly called; unlike IRB, running a file never echoes intermediate values on its own.
Example 2: puts, print, and p aren’t the same
These three output methods look similar but behave differently, which matters once you start debugging. Save this as output_methods.rb:
puts "Using puts:"
puts "line one", "line two"
print "Using print: "
print "no newline here"
print "\n"
p "Using p:"
p [1, 2, 3]
Output:
Using puts:
line one
line two
Using print: no newline here
"Using p:"
[1, 2, 3]
puts adds a newline after each argument, and prints each element of an array on its own line. print writes exactly what you give it with no trailing newline, so you’re responsible for adding your own "\n". p calls inspect on its argument before printing it — that’s why the string shows its quotation marks and the array shows its brackets and commas exactly as you’d type them in code. p is the method to reach for when debugging, because it shows the actual Ruby representation of a value, so nil prints as nil rather than as an empty string.
Example 3: Command-line arguments with ARGV
Real scripts often need input from the command line rather than hardcoded values. Save this as greet.rb:
#!/usr/bin/env ruby
name = ARGV[0] || "stranger"
puts "Hello, #{name}!"
Output (running ruby greet.rb with no arguments):
Hello, stranger!
ARGV is a plain Array that Ruby automatically fills with whatever you typed after the filename on the command line — running ruby greet.rb Alice would make ARGV equal to an array holding Alice, so ARGV[0] would hold that name and the greeting would change accordingly. Here, since no argument was given, ARGV[0] is nil, and nil is falsy, so || falls through to the default string. The first line, the shebang, is just a comment as far as Ruby’s parser is concerned — but on Unix-like systems it tells the shell which interpreter to use if the file is executed directly, which the next section covers.
What this looks like in a real terminal
Here’s the same examples run from an actual shell, including passing an argument, plus a short IRB session for comparison:
$ ruby hello.rb
Hello, Ruby!
This script has 4 lines of math.
$ ruby greet.rb
Hello, stranger!
$ ruby greet.rb Alice
Hello, Alice!
$ irb
irb(main):001:0> 1 + 1
=> 2
irb(main):002:0> name = "Ruby"
=> "Ruby"
irb(main):003:0> puts "Hi, #{name}!"
Hi, Ruby!
=> nil
irb(main):004:0> exit
Look closely at the last two lines of the IRB session: puts "Hi, #{name}!" prints Hi, Ruby! because that’s what puts does, but IRB then also shows => nil underneath. That’s IRB echoing the return value of the expression it just evaluated — and puts always returns nil. The printed text and the echoed return value are two completely different things, and mixing them up is a common beginner trap covered below.
How It Works Step by Step
When you run ruby greet.rb Alice, here’s the sequence:
- The shell finds the
rubyexecutable on yourPATHand starts it, passinggreet.rbandAliceas its own arguments. - Ruby recognizes the first non-flag argument,
greet.rb, as the script to run, and puts every argument after it — justAlicehere — into theARGVarray. - Ruby reads the entire file and parses it into an abstract syntax tree. If there’s a syntax error, such as a missing
endor an unclosed string, it’s caught right here and the script never executes at all. - The AST is compiled into YARV bytecode.
- The bytecode executes top to bottom inside the context of the top-level
mainobject: the shebang line is skipped as a comment,ARGV[0] || "stranger"is evaluated and assigned toname, and the finalputsline runs. - The process exits, returning an exit status of
0for success, or non-zero if an exception was raised.
IRB’s loop is similar but repeats per line instead of running the whole file once: it reads one statement, buffering extra lines if a block or string isn’t closed yet (shown by the continuation prompt changing from irb(main):001:0> to something like irb(main):002:1*), evaluates it against the same running main object so variables persist between lines, prints the inspected return value, and loops. That persistence is a key IRB-only behavior: a variable defined on line 1 is still available on line 20, which is exactly why IRB is such a good tool for iterative exploration — you build up state as you go, the way you can’t when a script simply starts over on every run.
Common Mistakes
Mistake 1: Assuming puts returns the string it printed
It’s easy to assume that because puts displays a value, it also returns that value — but puts always returns nil. Since a Ruby method implicitly returns the value of its last evaluated expression, putting a puts call at the end of a method silently makes that method return nil, even though it printed something useful first.
def shout(message)
puts message.upcase
end
result = shout("hello")
puts result.inspect
HELLO
nil
shout prints HELLO as a side effect, but its return value is whatever puts returns — nil — so result ends up nil instead of the shouted string. Fix it by making the value itself the method’s last expression, and let the caller decide whether to print it:
def shout(message)
message.upcase
end
result = shout("hello")
puts result
HELLO
The printed output looks identical here, but now result actually holds HELLO and can be reused, stored, or passed to another method, not just printed once and discarded.
Mistake 2: Forgetting execute permissions when running a script directly
Adding a shebang line doesn’t automatically make a file executable. Trying to run it with ./greet.rb before granting execute permission fails with a permissions error, not a Ruby error:
$ ./greet.rb
-bash: ./greet.rb: Permission denied
The fix is to mark the file executable once with chmod +x; after that, the shebang line lets the shell hand the file to ruby automatically:
$ chmod +x greet.rb
$ ./greet.rb
Hello, stranger!
If you’d rather skip this entirely, running ruby greet.rb always works regardless of file permissions or a shebang line — the shebang only matters for the ./greet.rb direct-execution style.
Mistake 3: Not noticing IRB is still waiting for more input
Because IRB reads one full expression at a time, an unclosed block leaves it waiting silently instead of erroring immediately. The prompt changes to signal this, but it’s easy to miss:
irb(main):001:0> [1, 2, 3].each do |n|
irb(main):002:1* puts n
At this point IRB hasn’t run anything yet — it’s still buffering, waiting for the block’s matching end. The 1 after the second colon, instead of 0, is the clue: it means "one block level deep." Typing end on the next line closes the block and runs it:
irb(main):003:1* end
1
2
3
=> [1, 2, 3]
If IRB ever seems "stuck," check the prompt for a trailing * and an incrementing nesting number — it almost always means you owe it a closing end, quote, or brace.
Best Practices
- Use
ruby -c filename.rbto catch syntax errors before you even run a script, especially in a longer file. - Reach for IRB, or a gem like
pry(a popular alternative REPL with extra debugging features), to test an idea in isolation before committing it to a script. - Give scripts a
.rbextension and, if they’ll be run directly with./, start them with#!/usr/bin/env rubyas the very first line. - Prefer
ruby filename.rbover./filename.rbduring development — it works with no permission setup and is one less thing to think about. - Use
pinstead ofputswhen debugging, so you can see exact types — the difference betweennil, an empty string, and the stringnilis invisible withputsbut obvious withp. - Don’t leave exploratory code sitting only in IRB and call it "done" — once an approach works there, move it into a real script or test file so it’s reproducible.
- Remember that variables and methods persist for the life of an IRB session; if results look wrong, consider whether stale state from an earlier line is the cause, and restart IRB when in doubt.
- Run
ruby -w filename.rboccasionally — it enables verbose warnings that flag things like uninitialized variables or ambiguous syntax without failing the run.
Practice Exercises
- Write a script called
double.rbthat reads a number fromARGV[0], converts it with.to_i, and prints double its value. Runningruby double.rb 21should print42. - Open
irband, one line at a time, assign an array of numbers to a variable, call.sumon it, and then call.sort!on it twice in a row. Compare what each line’s=>output shows you, paying close attention to what the second.sort!call returns. - Take the
greet.rbscript from Example 3, make sure it has a shebang line, make it executable withchmod, and confirm you can run it both asruby greet.rband as./greet.rb.
Summary
- Ruby has no separate compile step for you to run manually —
ruby filename.rbparses, compiles to YARV bytecode, and executes a script in one step. - IRB is a REPL: it reads one expression, evaluates it against a persistent top-level
mainobject, prints the expression’s inspected return value with=>, and loops. - A script only prints what you explicitly print; IRB additionally echoes every expression’s return value, which is a common source of confusion for beginners.
puts,print, andpdiffer in newline handling and whether they callinspect— usepfor debugging.ARGVgives a script access to command-line arguments as an array of strings.- A shebang line plus
chmod +xlets a script run directly as./script.rb; without execute permission, direct execution fails even with a correct shebang. ruby -cchecks syntax without running code, which is useful for catching typos early.
