Как я могу получить имя команды, вызванной для запросов на использование в Ruby?

Я написал маленький маленький Ruby script, который мне нравится. Я бы хотел улучшить его надежность, проверив правильное количество аргументов:

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

Конечно, это псевдокод. В любом случае, на C или С++ я мог бы использовать argv[0], чтобы получить имя, которое пользователь использовал для моей команды, называли ли они ее как ./myScript.rb или myScript.rb или /usr/local/bin/myScript.rb. В Ruby я знаю, что argv[0] является первым истинным аргументом, а ARGV не содержит имени команды. Есть ли способ получить это?

Ответ 1

Ruby имеет три способа дать нам имя вызываемого script:

#!/usr/bin/env ruby

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

Сохранение этого кода как "test.rb" и вызов его несколькими способами показывает, что script получает имя, которое было передано ему ОС. A script знает только, что говорит OS:

$ ./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

Вызов с помощью ~ ярлыка для $HOME во втором примере показывает, что ОС заменяет его расширенным путем, сопоставляя то, что находится в третьем примере. Во всех случаях это то, что передала ОС.

Связывание с файлом с использованием жестких и мягких ссылок показывает последовательное поведение. Я создал жесткую ссылку для test1.rb и мягкую ссылку для 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

Запуск ruby test.rb с любым из вариантов имени script возвращает согласованные результаты.

Если вам требуется только имя имени, вы можете использовать метод File basename с одной из переменных или разделить на разделитель и взять последний элемент.

$0 и __FILE__ имеют некоторые незначительные отличия, но для отдельных скриптов они эквивалентны.

puts File.basename($0)

Незначительное дополнение:

Есть несколько преимуществ использования наборов методов File.basename, File.extname и File.dirname. basename принимает необязательный параметр, который является расширением для полосы, поэтому, если вам нужно просто базовое имя без расширения

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

делает это без необходимости изобретать колесо или иметь дело с переменными или отсутствующими расширениями или возможностью некорректно обрезать цепи расширения ".rb.txt", например:

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" 

Ответ 2

этот ответ может немного запоздать, но у меня была одна и та же проблема, и принятый ответ мне не казался вполне удовлетворительным, поэтому я немного побеспокоился.

Меня беспокоило то, что $0 или $PROGRAM_NAME действительно не содержали правильную информацию о том, что пользователь имел напечатал. Если мой Ruby script находился в папке PATH, и пользователь ввел имя исполняемого файла (без каких-либо определений путей, таких как ./script или /bin/script), он всегда будет расширяться до полного пути.

Я думал, что это был дефицит Ruby, поэтому я попробовал то же самое с Python и, к моему огорчению, это ничем не отличалось.

Друг предложил мне взломать real thing в /proc/self/cmdline, а результат был: [ruby, /home/danyel/bin/myscript, arg1, arg2...] (разделен нулевым char). Злодей здесь execve(1), который расширяет путь до полного пути, когда он передает его интерпретатору.

Пример программы C:

#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);
}

Выход: `Использование:/home/danyel/bin/myscript FILE...

Чтобы доказать, что это действительно execve вещь, а не bash, мы можем создать фиктивный интерпретатор, который ничего не делает, кроме как распечатывать переданные ему аргументы:

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

Мы скомпилируем его и поместим в папку пути (или поместим полный путь после shebang) и создаем фиктивный script в ~/bin/myscript/

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

Теперь, в нашем 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);
}

Компиляция и запуск ./main:   переводчик   /Главная/Danyel/бен/MyScript   -v   /var/log/apache 2.log

Причина этого, скорее всего, в том, что если script находится в вашем PATH, а полный путь был не, интерпретатор распознает это как ошибку No such file, которую он делает если вы делаете: ruby myrubyscript --options arg1, и вы не находитесь в папке с этим script.

Ответ 3

Используйте $0 или $PROGRAM_NAME, чтобы получить имя файла, который в настоящее время выполняется.

Ответ 4

Это не совсем ответ на ваш вопрос, но похоже, что вы изобретаете колесо. Посмотрите на библиотеку optparse. Он позволяет вам определять ключи командной строки, аргументы и т.д., И он сделает все тяжелое поднятие для вас.