Làm cách nào để lấy tên lệnh được gọi cho lời nhắc sử dụng trong Ruby?


83

Tôi đã viết một đoạn mã Ruby nhỏ hay ho mà tôi khá thích. Tôi muốn cải thiện độ chắc chắn của nó bằng cách kiểm tra số lượng đối số thích hợp:

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

Tất nhiên đó là mã giả. Dù sao, trong C hoặc C ++, tôi có thể sử dụng argv[0]để lấy tên mà người dùng đã sử dụng để truy cập vào lệnh của tôi, cho dù họ gọi nó như thế nào ./myScript.rbhoặc myScript.rbhoặc /usr/local/bin/myScript.rb. Trong Ruby, tôi biết đó ARGV[0]là đối số true đầu tiên và ARGVkhông chứa tên lệnh. Có cách nào mà tôi có thể nhận được điều này?

Câu trả lời:


149

Ruby có ba cách để cung cấp cho chúng ta tên của tập lệnh được gọi:

#!/usr/bin/env ruby

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

Lưu mã đó dưới dạng "test.rb" và gọi nó theo một vài cách cho thấy rằng tập lệnh nhận được tên khi nó được Hệ điều hành chuyển cho nó. Một tập lệnh chỉ biết những gì hệ điều hành nói với nó:

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

Gọi nó bằng ~phím tắt $ HOME trong ví dụ thứ hai cho thấy Hệ điều hành thay thế nó bằng đường dẫn mở rộng, khớp với những gì trong ví dụ thứ ba. Trong mọi trường hợp, đó là những gì hệ điều hành đã chuyển vào.

Liên kết đến tệp bằng cả liên kết cứng và mềm cho thấy hành vi nhất quán. Tôi đã tạo một liên kết cứng cho test1.rb và một liên kết mềm cho 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

Khởi chạy ruby test.rbvới bất kỳ biến thể nào trên tên tập lệnh sẽ trả về kết quả nhất quán.

Nếu bạn chỉ muốn tên tệp được gọi, bạn có thể sử dụng basenamephương thức của Tệp với một trong các biến hoặc tách trên dấu phân cách và lấy phần tử cuối cùng.

$0__FILE__có một số khác biệt nhỏ nhưng đối với các tập lệnh đơn thì chúng tương đương nhau.

puts File.basename($0)

Có một số lợi ích cho việc sử dụng File.basename, File.extnameFile.dirnamebộ phương pháp. basenamenhận một tham số tùy chọn, là phần mở rộng để tách, vì vậy nếu bạn chỉ cần tên cơ sở mà không cần phần mở rộng

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

làm điều đó mà không phải phát minh lại bánh xe hoặc phải đối phó với các phần mở rộng có độ dài thay đổi hoặc bị thiếu hoặc khả năng cắt ngắn sai chuỗi phần mở rộng " .rb.txt" chẳng hạn:

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" 

Cảm ơn vì câu trả lời rất đầy đủ!
adam_0

2
Lưu ý rằng có những hành vi khác nhau nếu bạn sử dụng các tùy chọn được đề xuất trong một tập lệnh được bao gồm bằng cách sử dụng 'request' hoặc 'request_relative. Sử dụng $ 0 và $ PROGRAM_NAME trả về tên của tập lệnh gọi. Sử dụng tùy chọn FILE với gạch dưới đôi xung quanh (Bạn định dạng nó như thế nào trong một nhận xét?) Trả về tên của tập lệnh được bao gồm.
mike663

18

câu trả lời này có thể đến hơi muộn, nhưng tôi đã gặp vấn đề tương tự và câu trả lời được chấp nhận dường như không làm tôi hài lòng lắm, vì vậy tôi đã điều tra thêm một chút.

Điều làm tôi bận tâm là thực tế có $0hoặc $PROGRAM_NAMEkhông thực sự nắm giữ thông tin chính xác về những gì người dùng đã nhập . Nếu tập lệnh Ruby của tôi nằm trong thư mục PATH và người dùng đã nhập tên thực thi (không có bất kỳ định nghĩa đường dẫn nào như ./scripthoặc /bin/script), nó sẽ luôn mở rộng thành đường dẫn tổng.

Tôi nghĩ rằng đây là một trang web thiếu hụt của Ruby, vì vậy tôi đã thử tương tự với Python và với sự thất vọng của tôi ở đó, nó không khác gì.

Một người bạn đề nghị tôi một hack để tìm kiếm real thingtrong /proc/self/cmdline, và kết quả là: [ruby, /home/danyel/bin/myscript, arg1, arg2...](ngăn cách bởi null-char). Nhân vật phản diện ở đây là execve(1)mở rộng đường dẫn đến đường dẫn tổng thể khi nó chuyển nó cho một thông dịch viên.

Chương trình C ví dụ:

#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);
}

Đầu ra: ʻCách sử dụng: / home / danyel / bin / myscript FILE ...

Để chứng minh rằng đây thực sự là một sự execvevật và không phải từ bash, chúng ta có thể tạo một trình thông dịch giả không làm gì khác ngoài việc in ra các đối số được truyền cho nó:

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

Chúng tôi biên dịch nó và đặt nó trong một thư mục đường dẫn (hoặc đặt đường dẫn đầy đủ sau shebang) và tạo một tập lệnh giả trong ~/bin/myscript/

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

Bây giờ, trong main.c của chúng tôi:

#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);
}

Biên dịch và chạy ./main: thông dịch viên / home / danyel / bin / myscript -v /var/log/apache2.log

Lý do đằng sau điều này rất có thể là nếu tập lệnh nằm trong PATH của bạn và đường dẫn đầy đủ không được cung cấp, trình thông dịch sẽ nhận ra đây là No such filelỗi, điều này xảy ra nếu bạn làm vậy: ruby myrubyscript --options arg1và bạn không ở trong thư mục có tập lệnh đó .


3

Sử dụng $0hoặc $PROGRAM_NAMEđể lấy tên tệp hiện đang được thực thi.


Điều này cho tôi con đường hoàn chỉnh. Tôi muốn biết những gì người dùng đã nhập. Ví dụ: nếu tôi có /usr/local/bin/myScript/usr/local/binđang ở trong của tôi $PATH, và tôi chỉ cần nhập myScript, tôi nhận được /usr/local/bin/myScripttừ$0
adam_0

2
Làm thế nào về $0.split("/").last?
Pierrotlefou

7
Tôi không yêu cầu CHỈ LÀ tên chương trình, ý tôi là tôi muốn chính xác những gì người dùng đã nhập để chạy chương trình. Nếu họ nhập ./myScript, tôi muốn một biến cung cấp cho tôi ./myScript. Nếu họ gõ /usr/bin/local/myScript, tôi muốn chính xác điều đó. vv
adam_0

3

Đây không phải là một câu trả lời cho câu hỏi của bạn, nhưng có vẻ như bạn đang phát minh lại một bánh xe. Nhìn vào thư viện optparse . Nó cho phép bạn xác định các công tắc dòng lệnh, đối số, v.v. và nó sẽ làm tất cả những việc nặng nhọc cho bạn.


2
Cảm ơn, nhưng tôi không cần bất cứ điều gì phức tạp. Ý tôi là, tôi chỉ có 2 đối số, vì vậy nó là một trường hợp quá đơn giản để đảm bảo tất cả những điều đó.
adam_0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.