ถ้าคุณใช้ 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 ทุกคนหน่อย? จะช่วยให้บล็อกโต และคนมากขึ้นได้เรียนรู้ 🙂