Skip to content

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

  1. Feed the two lines alice 30 and bob 25 to four one-liners started as child Rubies: -a -n printing the second field, -p upcasing, -l -n appending !, and -a -n with BEGIN { } and END { } blocks summing the numbers. Print each command and its output.
  2. Run ruby -w on a file that reassigns a constant, calls a method with mismatched indentation of end, and uses if x = 1 — collect the three warnings from stderr and print them without the temp-folder path. Then run the same file with -W0 and 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.

ruby -a -n -e 'puts $F[1]'
    30
    25
ruby -p -e '$_.upcase!'
    ALICE 30
    BOB 25
ruby -l -n -e 'puts $_ + "!"'
    alice 30!
    bob 25!
ruby -a -n -e 'BEGIN { $sum = 0 }; $sum += $F[1].to_i; END { puts $sum }'
    55

See also