The command line is part of the language¶
Level: 201 · anyone who has typed ruby file.rb and wants to know what the other letters do — and which Python letters mean something else
One line: ruby takes flags for running code (-e), checking it (-c), warning about it (-w, -W:category), loading before it (-r, -I), turning a script into a filter (-n, -p, -a, -l), fixing the encoding (-E), switching engines and gems (--yjit, --disable-gems, --enable=frozen-string-literal) and dumping what the compiler made (--dump=insns); python3 has a counterpart for most, and -I, -s and -S mean different things in the two.
Every row below starts a child interpreter — RbConfig.ruby is the Ruby running the page, sys.executable its Python — with the flags shown, feeds it stdin when the flag reads input, and prints the child's stdout, stderr and exit status. A temporary folder holds three tiny files: ok.rb, bad.rb (a def with no end) and unused.rb (an assigned-but-unused local), and a lib/ folder with helper2.rb; their paths print as DIR. Read a row as a terminal session: the command, what came back.
Verified output of the_command_line_flags_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
1. ruby -e 'puts 1 + 1'
stdout: 2
exit: 0
2. ruby -c DIR/ok.rb
stdout: Syntax OK
exit: 0
2. ruby -c DIR/bad.rb
stderr: ruby: DIR/bad.rb:1: syntax errors found (SyntaxError) (first line only)
exit: 1
3. ruby -w DIR/unused.rb
stderr: DIR/unused.rb:2: warning: assigned but unused variable - x
exit: 0
4. ruby -W:deprecated -e 'p Warning[:deprecated]'
stdout: true
exit: 0
4. ruby -W:no-deprecated -e 'p Warning[:deprecated]'
stdout: false
exit: 0
4. ruby -W0 -e 'p $VERBOSE'
stdout: nil
exit: 0
4. ruby -w -e 'p $VERBOSE'
stdout: true
exit: 0
5. ruby -r json -e 'puts JSON.generate([1, {a: 2}])'
stdout: [1,{"a":2}]
exit: 0
6. ruby -I DIR/lib -e 'require "helper2"'
stdout: helper2 here
exit: 0
7. ruby -n -e 'puts $_.upcase'
stdin: "ab\ncd\n"
stdout: AB | CD
exit: 0
7. ruby -p -e '$_.upcase!'
stdin: "ab\ncd\n"
stdout: AB | CD
exit: 0
7. ruby -a -n -e 'puts $F[1]'
stdin: "a b c\nd e f\n"
stdout: b | e
exit: 0
7. ruby -n -e "puts $_ + '!'"
stdin: "ab\ncd\n"
stdout: ab | ! | cd | !
exit: 0
7. ruby -l -n -e "puts $_ + '!'"
stdin: "ab\ncd\n"
stdout: ab! | cd!
exit: 0
8. ruby -E UTF-8 -e 'p Encoding.default_external'
stdout: #<Encoding:UTF-8>
exit: 0
8. ruby -E ASCII-8BIT -e 'p Encoding.default_external'
stdout: #<Encoding:BINARY (ASCII-8BIT)>
exit: 0
9. ruby --yjit -e 'p RubyVM::YJIT.enabled?'
stdout: true
exit: 0
9. ruby -e 'p RubyVM::YJIT.enabled?'
stdout: false
exit: 0
10. ruby --disable-gems -e 'p defined?(Gem)'
stdout: nil
exit: 0
10. ruby -e 'p defined?(Gem)'
stdout: "constant"
exit: 0
11. ruby --enable=frozen-string-literal -e "p 'a'.frozen?"
stdout: true
exit: 0
11. ruby -e "p 'a'.frozen?"
stdout: false
exit: 0
12. ruby --dump=insns -e 'a = 1 + 2; puts a * 3'
instructions: putobject_INT2FIX_1_ putobject opt_plus setlocal_WC_0 putself getlocal_WC_0 putobject opt_mult opt_send_without_block leave
13. ruby -v -> starts with 'ruby 4.': true
13. ruby -h -> first line starts with 'Usage:': true
14. ruby -e 'p ARGV' -- -x y
stdout: ["-x", "y"]
exit: 0
15. ruby -s -e 'p $flag' -- -flag=5
stdout: "5"
exit: 0
Reading the output¶
Row 1, -e, runs a string; several -e flags concatenate into one program. Row 2, -c, checks syntax only: Syntax OK for the good file, and for the bad one exit status 1 with Prism's message on stderr — the first line says syntax errors found, the rest (dropped here) draws the offending line with a caret. Row 3, -w, turns on the warnings that are off by default; assigned but unused variable - x is the one most people meet first. Row 4 shows the finer switches: -W:deprecated and -W:no-deprecated flip one category, while -W0 sets $VERBOSE to nil (silence) and -w sets it to true. Row 5, -r json, requires a library before the program starts, and row 6, -I DIR/lib, prepends a folder to $LOAD_PATH so a bare require "helper2" finds it — the two flags a test runner uses as -Ilib -Itest. Row 7 is the Perl heritage: -n wraps the program in while gets … end with each line in $_; -p does the same and prints $_ afterwards; -a splits each line into $F; and -l chomps the newline on the way in and adds it back on the way out — compare the two puts $_ + "!" rows, where without -l the ! lands on its own line. Row 8, -E, sets the default external encoding, which is why this library runs every example as ruby -E UTF-8. Row 9 enables YJIT for the child only; row 10 starts Ruby without RubyGems, so Gem is not even defined (and startup is faster); row 11 makes every string literal frozen, the same effect as the # frozen_string_literal: true magic comment, but for the whole run. Row 12 asks the compiler to print its instruction sequence — the names show 1 + 2 compiled as putobject_INT2FIX_1_, putobject (the 2) and opt_plus, not folded to 3. Row 13 checks -v and -h only by their first words, since the rest names a version. Row 14 is --, which ends flag parsing so that -x reaches ARGV; row 15 is -s, which turns -flag=5 on the command line into a global $flag — a Perl idea that Python's -s has nothing to do with. -S looks the script up on PATH (so ruby -S rake finds the installed rake), and RUBYOPT holds flags every run should have (Not machine-checked here.).
Compared with Python¶
The counterparts line up by row. -c is Python's -e (row 1); a syntax check is -m py_compile file (row 2), which prints nothing on success and a SyntaxError on failure; there is no -w lint, so row 3 shows the warnings filter instead — -W error turns a warnings.warn into a failing exit, and plain running prints the warning to stderr and continues. Row 4 is -X dev, the development mode that switches on extra checks and warnings, next to -W ignore. -m module (row 5) runs a module as a script, a job Ruby splits between -r and -S. The letters that collide start at row 6: Python's -I means isolated — ignore environment variables, the user site directory, and the script's own folder on sys.path (safe_path: True) — which is why this library runs every twin with it; the include-a-folder job belongs to the PYTHONPATH variable. Row 7 has no -n/-p/-a/-l at all: the loop over sys.stdin is written out, and line.rstrip() does what -l did. Row 8 is -X utf8 (UTF-8 mode) beside sys.stdout.encoding, which this page's environment already pins to utf-8. Row 9: no JIT switch in 3.12, so the twin shows -O, the optimizer flag that sets __debug__ to False and removes assert statements; the experimental -X jit appears only in later interpreters built with it (Not machine-checked here.). Row 10, -S, skips the site module — the closest thing to --disable-gems, since site is what puts installed packages on sys.path. Row 11 is -OO, which additionally strips docstrings, a compile-time switch in the spirit of --enable=frozen-string-literal. Row 12 shows constant folding through co_consts (1000 + 2000 became 3000 at compile time, where Ruby left 1 + 2 as two pushes and an opt_plus); python3 -m dis prints the bytecode, but its text changes with every release, so the page does not key it (Not machine-checked here.). Row 14 needs no --: everything after the -c string is sys.argv, and sys.argv[0] is the string -c. Row 15 is the false friend: Python's -s disables the user site-packages directory. The Python library's -c is not the prompt ↗ page measures the -c flag against the REPL in more detail.
Verified output of the_command_line_flags_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. python3 -c 'print(1 + 1)'
stdout: 2
exit: 0
2. python3 -m py_compile DIR/ok.py
exit: 0
2. python3 -m py_compile DIR/bad.py
stderr: SyntaxError: '(' was never closed (last line only)
exit: 1
(stderr mentions 'SyntaxError': True)
3. python3 -W error -c "import warnings; warnings.warn('careful')"
stderr: UserWarning: careful (last line only)
exit: 1
3. python3 -c "import warnings; warnings.warn('careful')"
stderr: <string>:1: UserWarning: careful (last line only)
exit: 0
4. python3 -X dev -c 'import sys; print(sys.flags.dev_mode)'
stdout: True
exit: 0
4. python3 -W ignore -c "import warnings; warnings.warn('careful'); print('silenced')"
stdout: silenced
exit: 0
4. python3 -c 'import sys; print(sys.flags.dev_mode)'
stdout: False
exit: 0
5. python3 -m json.tool --compact
stdin: '[1, {"a": 2}]'
stdout: [1,{"a":2}]
exit: 0
6. python3 -I -c "import sys; print('isolated:', sys.flags.isolated, 'safe_path:', sys.flags.safe_path)"
stdout: isolated: 1 safe_path: True
exit: 0
6. python3 -c 'import helper2'
stdout: helper2 here
exit: 0
(the include-directory job is PYTHONPATH=DIR/lib, not a flag)
7. python3 -c 'import sys
for line in sys.stdin: print(line.rstrip().upper())'
stdin: 'ab\ncd\n'
stdout: AB | CD
exit: 0
(no -n, -p, -a or -l: the loop is written out)
8. python3 -X utf8 -c 'import sys; print(sys.flags.utf8_mode)'
stdout: 1
exit: 0
8. python3 -c 'import sys; print(sys.stdout.encoding)'
stdout: utf-8
exit: 0
9. python3 -O -c print(__debug__)
stdout: False
exit: 0
9. python3 -c print(__debug__)
stdout: True
exit: 0
(no JIT switch in 3.12; -X jit exists only in later builds made with it)
10. python3 -S -c "import sys; print(sys.flags.no_site, 'site' in sys.modules)"
stdout: 1 False
exit: 0
10. python3 -c "import sys; print(sys.flags.no_site, 'site' in sys.modules)"
stdout: 0 True
exit: 0
11. python3 -OO -c 'import json; print(json.dumps.__doc__ is None)'
stdout: True
exit: 0
11. python3 -c 'import json; print(json.dumps.__doc__ is None)'
stdout: False
exit: 0
12. python3 -c "print(3000 in compile('a = 1000 + 2000', '', 'exec').co_consts)"
stdout: True
exit: 0
(the compiler folded 1000 + 2000; -m dis shows the bytecode, whose text changes per release)
13. python3 -V -> starts with 'Python 3.': True
13. python3 -h -> first line starts with 'usage:': True
14. python3 -c 'import sys; print(sys.argv)' -x y
stdout: ['-c', '-x', 'y']
exit: 0
15. python3 -s -c "import sys; print('no_user_site:', sys.flags.no_user_site)"
stdout: no_user_site: 1
exit: 0
(-s means 'no user site-packages' here; Ruby's -s parses -flag=value switches)
Try it¶
- Feed the two lines
alice 30andbob 25to four one-liners started as child Rubies:-a -nprinting the second field,-pupcasing,-l -nappending!, and-a -nwithBEGIN { }andEND { }blocks summing the numbers. Print each command and its output. - Run
ruby -won a file that reassigns a constant, calls a method with mismatched indentation ofend, and usesif x = 1— collect the three warnings from stderr and print them without the temp-folder path. Then run the same file with-W0and show that stderr is empty.
Solution to 1
the_command_line_flags_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.
# Kata: four one-liners over the same two lines of input, each run as a child
# Ruby, each printing the command and what it produced.
require "open3"
INPUT = "alice 30\nbob 25\n"
def one_liner(flags, code)
out, = Open3.capture3(RbConfig.ruby, *flags, "-e", code, stdin_data: INPUT)
puts "ruby #{flags.join(" ")} -e '#{code}'"
out.each_line { |line| puts " #{line}" }
end
one_liner %w[-a -n], "puts $F[1]"
one_liner %w[-p], "$_.upcase!"
one_liner %w[-l -n], 'puts $_ + "!"'
one_liner %w[-a -n], "BEGIN { $sum = 0 }; $sum += $F[1].to_i; END { puts $sum }"
Verified output of the_command_line_flags_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
See also¶
requireloads once;loadruns every time — what-rand-Ido before the first line runsirbechoes every value; a script does not — the interactive way in, next to-e- Warnings and
-w— the whole list of what-wreports and how to capture it in-process - The bytecode you can see —
--dump=insnsfrom inside a program, withRubyVM::InstructionSequence ARGV,ENVand the exit status — what the child processes here are reporting back- Ruby text: the one-liner flags ↗ —
-n,-p,-a,-l,-iand-Fat length, with their Perl originals - Ruby text: the default external encoding follows the locale ↗ — why
-E UTF-8is on every command in this library - Python:
-cis not the prompt ↗ — the twin's-c, measured against the REPL - Common error messages — Twenty-nine errors you will meet in your first month of Ruby, each with the code that raises it, the…
rescueworks as a modifier, forStandardErroronly —expr rescue fallbackevaluatesexprand, if it raises aStandardError, yieldsfallbackinstead; it…- YJIT and ZJIT are switches, not defaults — MRI (
RUBY_ENGINE == "ruby") always runs YARV bytecode through its interpreter; YJIT (since 3.1) and ZJIT… - A Minitest test is a plain class — Minitest, a bundled gem, runs every
test_*method of aMinitest::Testsubclass in a seeded random…