Як я можу отримати назву команди, що викликається для підказок використання, у Ruby?


83

Я давно написав маленький приємний сценарій 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не містить назви команди. Чи є спосіб отримати це?

Відповіді:


149

Рубі має три способи дати нам ім'я викликаного сценарію:

#!/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з будь-яким із варіантів імені сценарію повертає несуперечливі результати.

Якщо вам потрібне лише викликане ім'я файлу, ви можете скористатися basenameметодом File з однією зі змінних або розділити розділювач і взяти останній елемент.

$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" 

Дякуємо за дуже повну відповідь!
adam_0

2
Зверніть увагу, що існує інша поведінка, якщо ви використовуєте запропоновані параметри у включеному сценарії за допомогою 'require' або 'require_relative. За допомогою $ 0 та $ PROGRAM_NAME повертається ім'я скрипта, що викликає. Використання опції FILE з оточуючими подвійними панелями (як це форматувати в коментарі?) Повертає ім’я включеного сценарію.
mike663

18

ця відповідь може прийти трохи пізно, але у мене була та сама проблема, і прийнята відповідь здалася мені не зовсім задовольняючою, тому я дослідив трохи далі.

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

Я думав, що це дефіцит Ruby, тому я спробував те саме з Python, і там, на мій жаль, нічим не відрізнявся.

Друг запропонував мені хак шукати real thingin /proc/self/cmdline, і результат був: [ruby, /home/danyel/bin/myscript, arg1, arg2...](відокремлений null-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) і створюємо фіктивний сценарій в ~/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: interpreter / home / danyel / bin / myscript -v /var/log/apache2.log

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


3

Використовуйте $0 або, $PROGRAM_NAMEщоб отримати ім'я файлу, яке наразі виконується.


Це дає мені повний шлях. Я хотів би знати, що ввів користувач. Наприклад, якщо у мене є /usr/local/bin/myScriptі /usr/local/binв моєму $PATH, і я просто набрати myScript, я отримую /usr/local/bin/myScriptвід$0
adam_0

2
Як щодо $0.split("/").last?
pierrotlefou

7
Я не питаю ПРОСТО назву програми, маю на увазі те, що я хочу саме те, що ввів користувач, щоб запустити програму. Якщо вони ввели ./myScript, я хочу змінну, яка дає мені ./myScript. Якщо вони набрали /usr/bin/local/myScript, я хочу саме це. тощо
adam_0

3

Це не зовсім відповідь на ваше запитання, але, схоже, ви винаходите колесо. Подивіться на бібліотеку optparse . Це дозволяє вам визначати перемикачі командного рядка, аргументи тощо, і це зробить все важке для вас.


2
Дякую, але мені не потрібно нічого такого складного. Я маю на увазі, що я маю лише 2 аргументи, тож надто простий випадок, щоб це все виправдати.
adam_0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.