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

Некоторое время назад я написал хороший маленький скрипт на 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 не содержит имя команды. Есть ли способ, которым я могу получить это?

72 голоса | спросил adam_0 29 Jam1000000amSat, 29 Jan 2011 05:50:31 +030011 2011, 05:50:31

4 ответа


0

У 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" 
ответил the Tin Man 29 Jam1000000amSat, 29 Jan 2011 11:26:13 +030011 2011, 11:26:13
0

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

Меня беспокоил тот факт, что $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 и вы не в папка с этим сценарием.

ответил Danyel 17 J000000Wednesday13 2013, 17:02:24
0

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

ответил pierrotlefou 29 Jam1000000amSat, 29 Jan 2011 05:56:48 +030011 2011, 05:56:48
0

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

ответил Chris Heald 29 Jam1000000amSat, 29 Jan 2011 08:24:55 +030011 2011, 08:24:55

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132