Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the "-S" flag used for in Ruby?

Tags:

ruby

I'm reading through the source code for the RBENV version manager, and I encountered this test in the spec file for the exec command:

@test "supports ruby -S <cmd>" {
  export RBENV_VERSION="2.0"

  # emulate `ruby -S' behavior
  create_executable "ruby" <<SH
#!$BASH
if [[ \$1 == "-S"* ]]; then
  found="\$(PATH="\${RUBYPATH:-\$PATH}" which \$2)"
  # assert that the found executable has ruby for shebang
  if head -n1 "\$found" | grep ruby >/dev/null; then
    \$BASH "\$found"
  else
    echo "ruby: no Ruby script found in input (LoadError)" >&2
    exit 1
  fi
else
  echo 'ruby 2.0 (rbenv test)'
fi
SH

  create_executable "rake" <<SH
#!/usr/bin/env ruby
echo hello rake
SH

  rbenv-rehash
  run ruby -S rake
  assert_success "hello rake"
}

There's already a separate test here which ensures that all arguments passed to rbenv exec are forwarded. And since the job of rbenv exec is just to ensure the correct Ruby version is used before forwarding any and all args to the originally-invoked command, I was confused why a separate test would be needed specifically for the -S flag.

I looked up the commit which introduced the test, but there wasn't any extra information explaining why this test was needed.

I looked up the man entry for Ruby and scrolled to the section on the -S flag. It contained the following info:

     -S             Makes Ruby use the PATH environment variable to search for script, unless its name begins with a slash.  This is used to emulate #! on
                    machines that don't support it, in the following manner:

                          #! /usr/local/bin/ruby
                          # This line makes the next one a comment in Ruby \
                            exec /usr/local/bin/ruby -S $0 $*

                    On some systems $0 does not always contain the full pathname, so you need the -S switch to tell Ruby to search for the script if necessary
                    (to handle embedded spaces and such).  A better construct than $* would be ${1+"$@"}, but it does not work if the script is being
                    interpreted by csh(1).

Maybe it's the wording of the man entry that is tripping me up, but I'm no more clear after reading the above than I was beforehand. Is there a concise, "Explain Like I'm 5" example of a problem that might be encountered while using Ruby, which only the -S flag could solve?

A few things in particular that I'm unclear on:

  1. The first sentence of the man description says "Makes Ruby use the PATH environment variable to search for script, unless its name begins with a slash." But which script are they referring to here- the script that I want to execute using the ruby command, or the ruby command script itself?

  2. I see (again from the man entry) that "This is used to emulate #! on machines that don't support it", but I don't understand how the former (i.e. using -S) leads to the latter (i.e. the emulation of #! described in the man entry).

like image 711
Richie Thomas Avatar asked Oct 28 '25 03:10

Richie Thomas


1 Answers

  1. Normally, when you invoke ruby foo.rb in a shell, the $PATH (or, equivalently, %PATH% on Windows) is searched, and the first ruby found (or foo.exe, or foo.com on Windows) is invoked. However, Ruby then only invokes foo.rb found in the current directory. ruby -S foo.rb will instruct Ruby to search the path for foo.rb as well.

  2. The "following manner" explains it. When you do just foo.rb (not ruby foo.rb), bash (and many other shells) will look find it in path, then try to execute it. To do this, it first tests if it is a binary executable. If it is not, it tests if it has the "shebang line" (i.e. if the first line starts with #!); if it does, it executes that instruction. When you have #!/usr/local/bin/env ruby at the start of foo.rb, it executes /usr/local/bin/env ruby foo.rb, which in turn finds ruby in path, which executes the foo.rb in the current directory.

    However, if a shell does not support the shebang, the code snippet in the manpage shows a workaround, assuming the shell automatically executes the file as a shell script, and assuming that the script is in path. As far as the shell is concerned, the first two lines are comments. As far as Ruby is concerned, the first three lines are comments (because the backslash will escape the newline, making the third line a continuation of the second). The shell will thus execute the third line, which calls Ruby and tells it to run the current script ($0); however, because some shells might not correctly identify where the script is, -S lets Ruby search for it on the path.

like image 175
Amadan Avatar answered Oct 29 '25 19:10

Amadan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!