Jak uzyskać nazwę polecenia wywoływanego do monitów użycia w języku Ruby?

83

Jakiś czas temu napisałem ładny, mały skrypt w Rubim, który raczej mi się podoba. Chciałbym poprawić jego niezawodność, sprawdzając odpowiednią liczbę argumentów:

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

Oczywiście to pseudokod. Tak czy inaczej, w C lub C ++ mogłem użyć argv[0], aby uzyskać nazwę użytkownika używany do dostać się do mojego polecenia, czy jak go nazwali ./myScript.rblub myScript.rblub /usr/local/bin/myScript.rb. W Rubim wiem, że ARGV[0]jest to pierwszy prawdziwy argument i ARGVnie zawiera nazwy polecenia. Czy jest jakiś sposób, żebym mógł to uzyskać?

adam_0
źródło

Odpowiedzi:

149

Ruby ma trzy sposoby podania nazwy wywoływanego skryptu:

#!/usr/bin/env ruby

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

Zapisanie tego kodu jako „test.rb” i wywołanie go na kilka sposobów pokazuje, że skrypt otrzymuje taką nazwę, jaka została mu przekazana przez system operacyjny. Skrypt wie tylko, co mówi mu system operacyjny:

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

Wywołanie go za pomocą ~skrótu do $ HOME w drugim przykładzie pokazuje, że system operacyjny zastępuje go rozszerzoną ścieżką, dopasowując to, co jest w trzecim przykładzie. We wszystkich przypadkach to właśnie przekazał system operacyjny.

Tworzenie linków do pliku przy użyciu zarówno twardych, jak i miękkich linków jest spójne. Utworzyłem link twardy do test1.rb i link miękki do 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

Uruchomienie ruby test.rbz dowolną odmianą nazwy skryptu zwraca spójne wyniki.

Jeśli chcesz tylko wywoływanej nazwy pliku, możesz użyć basenamemetody File z jedną ze zmiennych lub podzielić separator i wziąć ostatni element.

$0i __FILE__mają drobne różnice, ale dla pojedynczych skryptów są równoważne.

puts File.basename($0)

Istnieją pewne korzyści użyciu File.basename, File.extnameoraz File.dirnamezestaw metod. basenameprzyjmuje opcjonalny parametr, który jest rozszerzeniem strip, więc jeśli potrzebujesz tylko nazwy basename bez rozszerzenia

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

robi to bez ponownego wynalezienia koła lub konieczności radzenia sobie ze zmienną długością lub brakującymi przedłużeniami lub możliwością nieprawidłowego obcięcia łańcuchów przedłużających, .rb.txtna przykład:

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" 
Blaszany Człowiek
źródło
Dzięki za bardzo wyczerpującą odpowiedź!
adam_0
2
Zwróć uwagę, że jeśli używasz sugerowanych opcji w dołączonym skrypcie, używając opcji „require” lub „require_relative” zachowuje się inaczej. Użycie $ 0 i $ PROGRAM_NAME zwraca nazwę skryptu wywołującego. Użycie opcji PLIK z otaczającymi podwójnymi podkreśleniami (Jak sformatować to w komentarzu?) Zwraca nazwę dołączonego skryptu.
mike663
18

ta odpowiedź może przyjść trochę za późno, ale miałem ten sam problem, a zaakceptowana odpowiedź nie wydawała mi się całkiem satysfakcjonująca, więc poszukałem nieco dalej.

Co mi nie przeszkadzało to fakt, że $0albo $PROGRAM_NAMEtak naprawdę nie posiadają prawidłowe informacje o tym, co użytkownik miał wpisane . Jeśli mój skrypt Ruby znajdował się w folderze PATH, a użytkownik wprowadził nazwę pliku wykonywalnego (bez żadnych definicji ścieżek, takich jak ./scriptlub /bin/script), zawsze rozwijałby się on do ścieżki całkowitej.

Myślałem, że to niedobór Rubiego, więc spróbowałem tego samego z Pythonem i ku mojemu rozczarowaniu nie było inaczej.

Znajomy zasugerował mi, żebym poszukał real thingin /proc/self/cmdline, a wynik był następujący: [ruby, /home/danyel/bin/myscript, arg1, arg2...](oddzielone znakiem null-char). Ten złoczyńca jest tym, execve(1)który rozszerza ścieżkę do całkowitej ścieżki, kiedy przekazuje ją tłumaczowi.

Przykładowy program w 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);
}

Wynik: Użycie: / home / danyel / bin / myscript PLIK ...

Aby udowodnić, że to rzeczywiście jest execverzecz, a nie z basha, możemy stworzyć fałszywego interpretera, który nic nie robi, tylko wypisuje przekazane mu argumenty:

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

Kompilujemy go i umieszczamy w folderze ze ścieżką (lub umieszczamy pełną ścieżkę po shebang) i tworzymy fałszywy skrypt w ~/bin/myscript/

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

Teraz w naszym 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);
}

Kompilowanie i uruchamianie ./main: interpreter / home / danyel / bin / myscript -v /var/log/apache2.log

Przyczyną tego jest najprawdopodobniej to, że jeśli skrypt znajduje się w Twojej PATH, a pełna ścieżka nie została podana, interpreter rozpozna to jako No such filebłąd, co zrobi, jeśli to zrobisz: ruby myrubyscript --options arg1i nie ma Cię w folderze z tym skryptem .

Danyel
źródło
3

Użyj $0lub, $PROGRAM_NAMEaby uzyskać nazwę aktualnie wykonywanego pliku.

pierrotlefou
źródło
To daje mi pełną ścieżkę. Chciałbym wiedzieć, co wpisał użytkownik. Na przykład jeśli mam /usr/local/bin/myScripti /usr/local/binjest w moim $PATH, a po prostu myScriptpiszę, dostaję /usr/local/bin/myScriptod$0
adam_0
2
A co powiesz $0.split("/").last?
pierrotlefou
7
Nie proszę o TYLKO nazwę programu, chodzi mi o to, że chcę dokładnie to, co wpisał użytkownik, aby uruchomić program. Jeśli wpisali ./myScript, potrzebuję zmiennej, która mi daje ./myScript. Jeśli napisali /usr/bin/local/myScript, chcę dokładnie tego. itd.
adam_0
3

To nie jest do końca odpowiedź na twoje pytanie, ale brzmi to tak, jakbyś na nowo odkrywał koło. Spójrz na bibliotekę optparse . Pozwala zdefiniować przełączniki wiersza poleceń, argumenty itp. I wykona za Ciebie całą ciężką pracę.

Chris Heald
źródło
2
Dzięki, ale nie potrzebuję niczego tak skomplikowanego. Mam na myśli tylko 2 argumenty, więc sprawa jest zbyt prosta, żeby to wszystko uzasadniać.
adam_0