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

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

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

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

Ответы [ 4 ]

140 голосов
/ 29 января 2011

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

#!/usr/bin/env ruby

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

Сохранение этого кода в виде «test.rb» и вызов его двумя способами показывает, что сценарий получает имя в том виде, как оно было передано ему операционной системой. Сценарий знает только то, что говорит ОС:

$ ./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 с любым из вариантов имени сценария возвращает согласованные результаты.

Если вам нужно только имя вызываемого файла, вы можете использовать метод 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" 
16 голосов
/ 17 июля 2013

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

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

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

Друг предложил мне взломать для поиска real thing в /proc/self/cmdline, и результат был: [ruby, /home/danyel/bin/myscript, arg1, arg2...] (разделенный нулевым символом). Злодей здесь - 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) и создадим фиктивный скрипт в ~/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/apache2.log

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

3 голосов
/ 29 января 2011

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

2 голосов
/ 29 января 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...