skip to content

Script Launch & Exit

The ruby command runs a file or a one-liner, reads RUBYOPT and ARGV, and ends via exit, exit! or abort. Interviewers probe exit statuses, at_exit order and the __FILE__ == $0 idiom.

on this pageshow

explore

questions

6

In Ruby, what does the `if __FILE__ == $0` guard do, and what do ARGV and $PROGRAM_NAME hold?

level: juniorimportance: must knowfreq 55%

answer

  1. current file vs launched file
  2. $0 is an alias of $PROGRAM_NAME
  3. ARGV excludes the script name
  4. require runs top-level code once
  5. __END__ makes DATA in the main script

basics

~20 s

FILE is the current file's path and $0 (alias $PROGRAM_NAME) names the script Ruby launched. They match only in that launched file, so guarded code runs when executed directly, not when required. ARGV holds the arguments after the script name.

solid answer

~50 s

`__FILE__` is a keyword that evaluates to the path of the source file it appears in, while `$0` (same variable as `$PROGRAM_NAME`) names the program that `ruby` was started with, or `"-e"` for a one-liner. In the file you launch they are equal; in a file loaded by `require` or `require_relative` they differ, so `main if __FILE__ == $0` lets one file be both a library and a command. `ARGV` is an array of strings holding only what follows the script name: interpreter flags placed before the script are consumed by `ruby`, and the script name itself is not in it. `$0` is writable (assigning it changes the process title where the platform supports that), and a file ending in `__END__` exposes the text after it through the `DATA` constant, but only for the main script.

code

bash · 4 lines
bash
$ ruby -e 'p [__FILE__, $0, ARGV]' a b
["-e", "-e", ["a", "b"]]
$ ruby rotate_logs.rb /var/log/app -v
# ARGV inside rotate_logs.rb: ["/var/log/app", "-v"]

go deeper

for a junior

Recall the three names: FILE is this file, $0 is the launched script, ARGV is the argument list without the script name. State plainly when the guarded block runs and when it is skipped.

for a middle

Explain why the guard is false under require, what $0 is under ruby -e, and that flags after the script name land in ARGV. Mention DATA and END as a main-script-only feature.

for a senior

Show how you keep CLI files testable: a thin guard calling an entry method that takes an argv array and returns a status. Point out that wrappers and binstubs change $0 and break naive guards.

for a principal

Frame the idiom as a packaging decision: small scripts can stay dual-use, but tools shared across teams deserve an explicit executable plus a library entry point, so launch style never changes behaviour.

## Three names a running script can see When `ruby rotate_logs.rb /var/log/app --keep 7` starts, Ruby exposes three related pieces of information, and confusing them is the root of most bugs in small command-line tools. | Name | Kind | Holds in `rotate_logs.rb` | Holds in a file it requires | |---|---|---|---| | `__FILE__` | keyword | `"rotate_logs.rb"` | that file's own path | | `$0` / `$PROGRAM_NAME` | global variable | `"rotate_logs.rb"` | still `"rotate_logs.rb"` | | `ARGV` (alias `$*`) | constant, an `Array` of `String` | `["/var/log/app", "--keep", "7"]` | the same array | - **`__FILE__`** is resolved at parse time for each file. In the launched script it is the path exactly as it was given on the command line; in a file loaded through `require` it is the path Ruby resolved, usually absolute. - **`$0`** and **`$PROGRAM_NAME`** are two names for the same variable. For `ruby -e '...'` its value is `"-e"`. - **`ARGV`** never contains the interpreter's own flags or the script name. Anything after the script name, even `-v`, is passed to the script untouched. ## How the guard works ```ruby # rotate_logs.rb module LogRotator def self.run(dir, keep:) # ...rename app.log to app.log.1 and prune old files... true end end if __FILE__ == $PROGRAM_NAME dir = ARGV.fetch(0) { abort "usage: #{$PROGRAM_NAME} DIR" } exit LogRotator.run(dir, keep: 7) end ``` 1. Run directly, `__FILE__` and `$PROGRAM_NAME` are both `"rotate_logs.rb"`, so the block reads `ARGV` and runs. 2. Required from a test (`require_relative "rotate_logs"`), the file's top-level code still executes once, defining `LogRotator`, but `$PROGRAM_NAME` names the test runner's script, so the command-line block is skipped. The guard does not stop Ruby from loading the file twice; that is the job of `require`'s once-only rule, a separate mechanism. It only decides whether the command part runs. ## When the comparison surprises you - **Wrappers and binstubs.** If a wrapper script calls `load "rotate_logs.rb"`, then `$0` names the wrapper and the guard is false. Keep the real entry point in a method (`LogRotator::CLI.start(ARGV)`) that the wrapper can call directly. - **Different spellings of one path.** Code that compares against a path built elsewhere should expand both sides: `File.expand_path(__FILE__) == File.expand_path($PROGRAM_NAME)`. - **One-liners.** Under `ruby -e`, both `__FILE__` and `$0` are `"-e"`. ## `$0` is writable `$0` is a hooked variable: assigning `$0 = "rotate_logs: pruning"` updates the process title where the platform allows it, so a long job can report its phase to tools that list processes. `Process.argv0` still returns the original name after such an assignment. ## `__END__` and `DATA` A line containing only `__END__` stops parsing. If the **main script** contains it, Ruby defines the constant `DATA`, a `File` opened on that script and positioned just after the `__END__` line: ```ruby DATA.each_line { |pattern| puts "would rotate #{pattern.chomp}" } __END__ *.log *.err ``` - A required file may also contain `__END__`, which ends its code, but `DATA` is only set for the program Ruby launched. - `DATA` is unrelated to the core `Data` class that builds value objects with `Data.define`. ## Testing the command part Move the command logic into a method that takes an array (`def self.start(argv)`) and call it with `ARGV` inside the guard. A test then calls `start(["tmp/logs"])` without touching the real `ARGV` or the process title, and asserts on the returned value. ## `ARGV` is an ordinary, mutable array - `ARGV` and `$*` refer to the same `Array`; every element is a `String`, so numbers must be converted (`Integer(ARGV[1])`). - `ARGV.shift` removes and returns the first argument, a common way to read a subcommand. - `OptionParser#parse!` from the `optparse` library removes the options it recognises from `ARGV` in place, leaving only the positional arguments. - Because it is global and mutable, code deep inside a library should not read `ARGV`; pass the values in instead. ## Interview traps in one list 1. Expecting the script name at `ARGV[0]`, as in a C program's `argv`. 2. Believing the guard prevents a file from being loaded twice. 3. Reading `DATA` from a required file. 4. Forgetting that under `ruby -e` there is no script file, so `__FILE__` is `"-e"` and paths built from it are meaningless.

  • Why can `__FILE__ == $0` be false even though you meant to run the file as a program?
    `$0` names whatever Ruby launched. If a wrapper, binstub or another script uses `load` or `require` on your file, `$0` is the wrapper's name and the guard is false. The robust design is an explicit entry method, such as `CLI.start(ARGV)`, that the wrapper calls, with the guard kept only as a convenience for direct runs.
  • What is `DATA`, and why does a required file with its own `__END__` not get one?
    `DATA` is a `File` opened on the main script and positioned after its `__END__` line, so the script can carry a small embedded data section. Ruby defines it only for the program it launched; `__END__` in a required file just ends that file's code and creates nothing, so `DATA` there is either the main script's or a `NameError`.
  • How would you make the command-line branch of such a file testable?
    Keep the branch to one line, `exit CLI.start(ARGV)`, and put the work in `CLI.start(argv)`, which takes an array and returns a boolean or a status. Tests call `CLI.start(["tmp/logs"])` directly, so they neither mutate the real `ARGV` nor trigger `exit` inside the test process.

The guard is like a speech that begins only when the speaker is the person the audience came to see: invited as a guest (required), the same person sits quietly and just lends their expertise.

saying these in an interview costs you the question

  • ARGV[0] is the script name, as argv[0] is in C
  • __FILE__ and $0 are always equal in every file
  • The guard stops require from loading the file a second time
  • Flags placed after the script name are consumed by ruby itself
  • DATA reads the __END__ section of whichever file mentions it
open as a page

In Ruby, how do exit, exit! and abort differ in exit status, SystemExit, ensure clauses and at_exit handlers?

level: middleimportance: must knowfreq 45%

basics

~20 s

exit raises SystemExit (default status 0), so ensure clauses and at_exit handlers run and it can be rescued. abort prints its message to $stderr and raises SystemExit with status 1. exit! ends the process at once (default status 1), skipping ensure and at_exit.

open as a page

In Ruby, in what order do at_exit handlers run, and how can a handler read or change the exit status?

level: middleimportance: should knowfreq 33%

basics

~20 s

at_exit handlers run in reverse order of registration when the program ends. Inside one, $! holds the exception that ended the program (a SystemExit after exit, nil after a normal finish), and calling exit there replaces the final status.

open as a page

In Ruby's command line, what do the -e, -n, -p, -l and -a flags do in a one-liner over a log file?

level: middleimportance: should knowfreq 28%

basics

~20 s

-e runs code given on the command line; -n wraps it in a while gets loop with each line in $; -p does the same and prints $ after every pass; -l chops line endings and sets $; -a splits each line into $F.

open as a page

A cron-run Ruby log-rotation script logs its failures, but cron always sees status 0; what in the script decides the exit status?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Only how the program ends: finishing normally, even after a rescued and logged error, returns 0. The script must end with exit(false), exit 1 or abort, nothing may cancel that SystemExit or override it in at_exit, and statuses wrap past 255.

open as a page

In Ruby, what does the RUBYOPT environment variable do, which switches may it carry, and when would you run ruby --disable=rubyopt?

level: seniorimportance: nice to knowfreq 16%

basics

~20 s

RUBYOPT adds command-line options to every ruby process that inherits the environment. Only a safe subset is allowed (-d, -E, -I, -K, -r, -U, -v, -w, -W and some long options); -e, -n or -p raise RuntimeError. --disable=rubyopt ignores it.

open as a page