How can I get the name of the command called for usage prompts in Ruby?

RubyCommand Line

Ruby Problem Overview


I wrote a nice little Ruby script a while back that I'm rather fond of. I'd like to improve its robustness by checking for the proper number of arguments:

if ARGV.length != 2 then
  puts "Usage: <command> arg1 arg2"
end

Of course that's pseudocode. Anyways, in C or C++ I could use argv[0] to get the name that the user used to get to my command, whether they called it like ./myScript.rb or myScript.rb or /usr/local/bin/myScript.rb. In Ruby, I know that ARGV[0] is the first true argument, and ARGV does not contain the command name. Is there any way that I can get this?

Ruby Solutions


Solution 1 - Ruby

Ruby has three ways of giving us the name of the called script:

#!/usr/bin/env ruby

puts "$0            : #{$0}"
puts "__FILE__      : #{__FILE__}"
puts "$PROGRAM_NAME : #{$PROGRAM_NAME}"

Saving that code as "test.rb" and calling it a couple ways shows that the script receives the name as it was passed to it by the OS. A script only knows what the OS tells it:

$ ./test.rb 
$0            : ./test.rb
__FILE__      : ./test.rb
$PROGRAM_NAME : ./test.rb

$ ~/Desktop/test.rb 
$0            : /Users/ttm/Desktop/test.rb
__FILE__      : /Users/ttm/Desktop/test.rb
$PROGRAM_NAME : /Users/ttm/Desktop/test.rb

$ /Users/ttm/Desktop/test.rb 
$0            : /Users/ttm/Desktop/test.rb
__FILE__      : /Users/ttm/Desktop/test.rb
$PROGRAM_NAME : /Users/ttm/Desktop/test.rb

Calling it using the ~ shortcut for $HOME in the second example shows the OS replacing it with the expanded path, matching what is in the third example. In all cases it's what the OS passed in.

Linking to the file using both hard and soft links shows consistent behavior. I created a hard link for test1.rb and a soft link for test2.rb:

$ ./test1.rb 
$0            : ./test1.rb
__FILE__      : ./test1.rb
$PROGRAM_NAME : ./test1.rb

$ ./test2.rb 
$0            : ./test2.rb
__FILE__      : ./test2.rb
$PROGRAM_NAME : ./test2.rb

Launching ruby test.rb with any of the variations on the script name returns consistent results.

If you only want the called filename, you can use File's basename method with one of the variables or split on the delimiter and take the last element.

$0 and __FILE__ have some minor differences but for single scripts they're equivalent.

puts File.basename($0)

There are some benefits to using the File.basename,File.extname and File.dirname suite of methods. basename takes an optional parameter, which is the extension to strip, so if you need just the basename without the extension

File.basename($0, File.extname($0)) 

does it without reinventing the wheel or having to deal with variable-length or missing extensions or the possibility of incorrectly truncating extension chains ".rb.txt" for instance:

ruby-1.9.2-p136 :004 > filename = '/path/to/file/name.ext'
 => "/path/to/file/name.ext" 
ruby-1.9.2-p136 :005 > File.basename(filename, File.extname(filename))
 => "name" 
ruby-1.9.2-p136 :006 > filename = '/path/to/file/name.ext' << '.txt'
 => "/path/to/file/name.ext.txt" 
ruby-1.9.2-p136 :007 > File.basename(filename, File.extname(filename))
 => "name.ext" 

Solution 2 - Ruby

this answer might come a bit late, but I have had the same issue and the accepted answer didn't seem quite satisfying to me, so I investigated a bit further.

What bothered me was the fact that $0 or $PROGRAM_NAME did not really hold the correct information about what the user had typed. If my Ruby script was in a PATH folder and the user entered the executable name (without any path definitions such as ./script or /bin/script), it would always expand to the total path.

I thought this was a Ruby deficite, so I tried the same with Python and to my chagrin there, it was no different.

A friend suggested me a hack to look for the real thing in /proc/self/cmdline, and the result was: [ruby, /home/danyel/bin/myscript, arg1, arg2...] (separated by the null-char). The villain here is execve(1) which expands the path to the total path when it passes it to an interpreter.

Example C program:

#include <stdlib.h>
#include <unistd.h>

extern char** environ;
int main() {
  char ** arr = malloc(10 * sizeof(char*));
  arr[0] = "myscript";
  arr[1] = "-h";
  arr[2] = NULL;
  execve("/home/danyel/bin/myscript", arr, environ);
}

Output: `Usage: /home/danyel/bin/myscript FILE...

To prove that this is indeed a execve thing and not from bash, we can create a dummy interpreter that does nothing but print out the arguments passed to it:

// interpreter.c
int main(int argc, const char ** argv) {
  while(*argv)
    printf("%s\n", *(argv++));
}

We compile it and put it in a path folder (or put the full path after the shebang) and create a dummy script in ~/bin/myscript/

#!/usr/bin/env interpreter
Hi there!

Now, in our main.c:

#include <stdlib.h>

extern char** environ;
int main() {
  char ** arr = malloc(10 * sizeof(char*));
  arr[0] = "This will be totally ignored by execve.";
  arr[1] = "-v";
  arr[2] = "/var/log/apache2.log";
  arr[3] = NULL;
  execve("/home/danyel/bin/myscript", arr, environ);
}

Compiling and running ./main: interpreter /home/danyel/bin/myscript -v /var/log/apache2.log

The reason behind this most likely is that if the script is in your PATH and the full path were not provided, the interpreter would recognize this as a No such file error, which it does if you do: ruby myrubyscript --options arg1 and you're not in the folder with that script.

Solution 3 - Ruby

Use $0or $PROGRAM_NAME to get the file name that is currently being executed.

Solution 4 - Ruby

This isn't quite an answer to your question, but it sounds like you're reinventing a wheel. Look at the optparse library. It lets you define command line switches, arguments, etc, and it'll do all the heavy lifting for you.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionadam_0View Question on Stackoverflow
Solution 1 - Rubythe Tin ManView Answer on Stackoverflow
Solution 2 - RubyDanyelView Answer on Stackoverflow
Solution 3 - RubypierrotlefouView Answer on Stackoverflow
Solution 4 - RubyChris HealdView Answer on Stackoverflow