Как я могу получить имя команды, вызванной для запросов на использование в 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. Он позволяет вам определять ключи командной строки, аргументы и т.д., И он сделает все тяжелое поднятие для вас.