Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

สร้าง Mini Shell ใน Ruby ด้วยเพียง 25 บรรทัด

ถ้าคุณใช้ Linux หรือ Mac ทุกครั้งที่เปิด terminal คุณใช้ shell application

Shell คือ interface ช่วย execute คำสั่งในระบบ

Shell โฮสต์ environment variables และมีฟีเจอร์ประโยชน์อย่าง command history และ auto-completion

ถ้าคุณชอบเรียนรู้ว่าสิ่งต่างๆ ทำงานภายใต้ฝาฝน (under the hood) โพสต์นี้เหมาะสำหรับคุณ!

Shell ทำงานอย่างไร?

มา思考 shell จริงๆ คืออะไร:

ก่อนหน้า มี prompt พร้อมข้อมูลเพิ่ม เช่น user ปัจจุบันและ directory ปัจจุบัน แล้วคุณพิมพ์คำสั่ง กด enter ผลลัพธ์แสดงบนหน้าจอ

聽起來 basic แต่ไม่ได้นึกถึงบางอย่าง?

ถ้าคุณคิดถึง pry คุณถูกต้อง!

Shell คือ REPL (Read-Eval-Print-Loop) สำหรับ operating system

รู้แล้วเราสามารถ เขียนเวอร์ชันแรกของ shell ได้:

prompt = "> "
print prompt
while (input = gets.chomp)
  break if input == "exit"
  system(input)
  print prompt
end

ได้ minimal แต่ functional shell สามารถปรับปรุงด้วย library ที่หลาย REPL-like apps ใช้

Library นั้นคือ Readline

ใช้ Library Readline

Readline อยู่ใน Ruby Standard Library ไม่ต้องติดตั้ง แค่ require

ข้อดีคือเก็บ command history ให้อัตโนมัติ

ยังจัดการพิมพ์ command prompt และสิ่งอื่นๆ ให้

Shell v2 ใช้ Readline:

require 'readline'
while input = Readline.readline("> ", true)
  break if input == "exit"
  system(input)
end

ดีขึ้น! กำจัด puts สองครั้งสำหรับ prompt และเข้าถึงความสามารถของ Readline เช่น keyboard shortcuts ลบคำ (CTRL + W) หรือค้นหา history (CTRL + R)!

เพิ่มคำสั่งพิมพ์ history เต็ม:

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

Fun fact: ลองรันใน pry จะได้ history ของ pry! เพราะ pry ใช้ Readline และ Readline::HISTORY เป็น shared state

ตอนนี้พิมพ์ hist ได้ history 🙂

เพิ่ม Auto-Completion

ขอบคุณ auto-completion ของ shell คุณพิมพ์น้อยลง Readline ทำให้ integration ง่าย

เริ่ม auto-complete คำสั่งจาก history:

ตัวอย่าง:

comp = proc { |s| Readline::HISTORY.grep(/^#{Regexp.escape(s)}/) }
Readline.completion_append_character = " "
Readline.completion_proc = comp
## rest of the code goes here ##

กับโค้ดนี้ ควร auto-complete คำสั่งที่พิมพ์ไปแล้วด้วย <tab> key ต่อไปเพิ่ม directory auto-completion

ตัวอย่าง:

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 คืนรายการ candidates ที่เป็นไปได้ ตรงนี้เช็คว่า string ที่พิมพ์เป็นส่วนของชื่อ directory ด้วย Dir.glob Readline จัดการส่วนที่เหลือ!

Implement เมธอด System

ตอนนี้คุณมี shell ทำงาน พร้อม history และ auto-completion ไม่แย่สำหรับ 25 บรรทัด 🙂

แต่มีอย่างอยากขุดลึก เพื่อให้ insight เกี่ยวกับการ execute คำสั่งจริงๆ

ทำโดยเมธอด system ใน C เมธอดนี้ส่งคำสั่งไป /bin/sh ซึ่งเป็น shell application มา看看 implement สิ่งที่ /bin/sh ทำใน Ruby

หมายเหตุ: นี้ทำงานได้แค่ Linux / Mac 🙂

เมธอด system:

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

เกิดอะไร: fork สร้าง copy ใหม่ของ process ปัจจุบัน แล้ว process นี้ถูกแทนที่ด้วยคำสั่งที่อยากรันผ่าน exec นี่คือ pattern ทั่วไปใน Linux programming

ถ้าไม่ fork process ปัจจุบันจะถูกแทนที่ หมายถึงตอนคำสั่ง (ls, cd หรืออะไรก็ตาม) จบ โปรแกรม Ruby ของคุณจะจบไปด้วย

เห็นได้ที่นี่:

def system(command)
  exec(command)
end

system('ls')
# This code will never run!
puts "after system"

สรุป

ในบทความนี้ คุณเรียนรู้ว่า shell คือ REPL-like interface (คิดถึง irb / pry) สำหรับ interact กับระบบ เรียนรู้สร้าง shell ของตัวเองด้วย Readline library ที่มีฟีเจอร์ built-in อย่าง history และ auto-completion (แตคุณต้องกำหนดว่ามันทำงานยังไง)

และหลังจากนั้น เรียนรู้ pattern fork + exec ที่ใช้ทั่วไปใน Linux programming projects

ถ้าชอบบทความนี้ ช่วยแชร์ให้เพื่อน Ruby ทุกคนหน่อย? จะช่วยให้บล็อกโต และคนมากขึ้นได้เรียนรู้ 🙂