Computer >> Máy Tính >  >> Lập trình >> Ruby

Viết một Shell trong 25 dòng mã Ruby

Nếu bạn sử dụng Linux hoặc Mac, mỗi khi bạn mở một thiết bị đầu cuối, bạn đang sử dụng một ứng dụng shell.

Shell là một giao diện giúp bạn thực thi các lệnh trong hệ thống của mình.

Trình bao lưu trữ các biến môi trường và có các tính năng hữu ích như lịch sử lệnh và tự động hoàn thành.

Nếu bạn là kiểu người thích tìm hiểu cách mọi thứ hoạt động một cách bí mật, thì bài đăng này sẽ rất phù hợp với bạn!

Shell hoạt động như thế nào?

Để xây dựng ứng dụng shell của riêng chúng ta, chúng ta hãy nghĩ về shell thực sự là gì:

Đầu tiên, có một lời nhắc, thường có thêm một số thông tin như người dùng hiện tại và thư mục hiện tại của bạn, sau đó bạn nhập lệnh và khi bạn nhấn enter, kết quả sẽ hiển thị trên màn hình của bạn.

Vâng, điều đó nghe có vẻ khá cơ bản, nhưng điều này không gợi cho bạn điều gì đó sao?

Nếu bạn đang nghĩ đến pry thì bạn đúng!

Về cơ bản, một shell trong REPL (Read-Eval-Print-Loop) cho hệ điều hành của bạn.

Biết rằng chúng tôi có thể viết phiên bản shell đầu tiên của mình :

prompt = "> "

print prompt

while (input = gets.chomp)
  break if input == "exit"

  system(input)
  print prompt
end

Điều này sẽ cung cấp cho chúng ta một lớp vỏ tối thiểu, nhưng đầy đủ chức năng. Chúng tôi có thể cải thiện điều này bằng cách sử dụng thư viện mà nhiều ứng dụng giống REPL khác sử dụng.

Thư viện đó được gọi là Readline .

Sử dụng Thư viện Readline

Readline là một phần của Thư viện Chuẩn Ruby, vì vậy không cần cài đặt gì cả, bạn chỉ cần require nó.

Một trong những lợi thế của việc sử dụng Readline là nó có thể lưu giữ lịch sử lệnh tự động cho chúng tôi.

Nó cũng có thể đảm nhận việc in dấu nhắc lệnh và nhiều thứ khác.

Đây là v2 của shell của chúng tôi, lần này sử dụng Readline :

require 'readline'

while input = Readline.readline("> ", true)
  break if input == "exit"

  system(input)
end

Điều này thật tuyệt, chúng tôi đã loại bỏ hai puts để có lời nhắc và bây giờ chúng tôi có quyền truy cập vào một số khả năng mạnh mẽ từ Readline . Ví dụ:chúng ta có thể sử dụng phím tắt để xóa một từ (CTRL + W ) hoặc thậm chí tìm kiếm lịch sử (CTRL + R )!

Hãy thêm một lệnh mới để in toàn bộ lịch sử:

require 'readline'

while input = Readline.readline("> ", true)
  break                       if input == "exit"
  puts Readline::HISTORY.to_a if input == "hist"

  # Remove blank lines from history
  Readline::HISTORY.pop if input == ""

  system(input)
end

Thực tế thú vị:Nếu bạn thử mã này trong pry, bạn sẽ nhận được lịch sử lệnh của pry! Lý do là pry cũng đang sử dụng ReadlineReadline::HISTORY là trạng thái được chia sẻ.

Bây giờ bạn có thể nhập hist để lấy lịch sử lệnh của bạn 🙂

Thêm tự động hoàn thành

Nhờ tính năng tự động hoàn thành trình bao yêu thích của bạn, bạn sẽ có thể tiết kiệm được rất nhiều thao tác nhập. Readline giúp bạn thực sự dễ dàng tích hợp tính năng này vào trình bao của mình.

Hãy bắt đầu bằng cách tự động hoàn thành các lệnh từ lịch sử của chúng tôi.

Ví dụ :

comp = proc { |s| Readline::HISTORY.grep(/^#{Regexp.escape(s)}/) }

Readline.completion_append_character = " "
Readline.completion_proc = comp

## rest of the code goes here ##

Với mã này, bạn có thể tự động hoàn thành các lệnh đã nhập trước đó bằng cách nhấn <tab> Chìa khóa. Bây giờ chúng ta hãy tiến hành bước này thêm một bước nữa và thêm tính năng tự động hoàn thành thư mục.

Ví dụ :

comp = proc do |s|
  directory_list = Dir.glob("#{s}*")

  if directory_list.size > 0
    directory_list
  else
    Readline::HISTORY.grep(/^#{Regexp.escape(s)}/)
  end
end

completion_proc trả về danh sách các ứng cử viên có thể có, trong trường hợp này, chúng tôi chỉ cần kiểm tra xem chuỗi đã nhập có phải là một phần của tên thư mục hay không bằng cách sử dụng Dir.glob . Readline sẽ lo phần còn lại!

Triển khai Phương pháp Hệ thống

Bây giờ bạn sẽ có một trình bao đang hoạt động, với lịch sử và tự động hoàn thành, không quá tệ cho 25 dòng mã 🙂

Nhưng có một điều mà tôi muốn tìm hiểu sâu hơn để bạn có thể hiểu thêm về những gì đang diễn ra đằng sau hậu trường của việc thực thi một lệnh.

Điều này được thực hiện bởi hệ thống system , trong C, phương thức này chỉ gửi lệnh của bạn tới /bin/sh , là một ứng dụng shell. Hãy xem cách bạn có thể triển khai những gì /bin/sh trong Ruby.

Lưu ý :Điều này sẽ chỉ hoạt động trên Linux / Mac 🙂

Phương pháp hệ thống:

def system(command)
  fork {
    exec(command)
  }
end

Điều xảy ra ở đây là fork tạo một bản sao mới của quy trình hiện tại, sau đó quy trình này được thay thế bằng lệnh mà chúng tôi muốn chạy qua exec phương pháp. Đây là một mẫu rất phổ biến trong lập trình Linux.

Nếu bạn không fork thì quy trình hiện tại sẽ được thay thế, có nghĩa là khi bạn đang chạy lệnh (ls , cd hoặc bất cứ điều gì khác) được thực hiện thì chương trình Ruby của bạn sẽ kết thúc với nó.

Bạn có thể thấy điều đó đang xảy ra tại đây:

def system(command)
  exec(command)
end

system('ls')

# This code will never run!
puts "after system"

Kết luận

Trong bài đăng này, bạn đã biết rằng shell là một giao diện giống như REPL (hãy nghĩ đến irb / pry ) để tương tác với hệ thống của bạn. Bạn cũng đã học cách xây dựng trình bao của riêng mình bằng cách sử dụng Readline mạnh mẽ thư viện, cung cấp nhiều tính năng tích hợp sẵn như lịch sử và tự động hoàn thành (nhưng bạn phải xác định cách thức hoạt động).

Và sau đó, bạn đã tìm hiểu về fork + exec mẫu thường được sử dụng trong các dự án lập trình Linux.

Nếu bạn thích bài đăng này, bạn có thể giúp tôi một việc và chia sẻ nó với tất cả những người bạn Ruby của bạn không? Nó sẽ giúp blog phát triển và nhiều người có thể học hơn 🙂