แม้ว่าการเขียนสคริปต์อรรถประโยชน์เพียงเล็กน้อยเป็นเรื่องสนุก แต่บางครั้งคุณจำเป็นต้องเขียนแอปพลิเคชันบรรทัดคำสั่งที่ซื่อสัตย์ต่อพระเจ้าอย่างแท้จริง หนึ่งที่รับข้อโต้แย้งและเล่นได้อย่างดีกับอนุสัญญายูนิกซ์สำหรับอินพุต เอาต์พุต การรายงานข้อผิดพลาด ฯลฯ
โชคดีที่ Ruby มีหน่วยการสร้างทั้งหมดที่คุณต้องการสำหรับแอปพลิเคชันบรรทัดคำสั่งอย่างง่ายดาย ในโพสต์นี้ ฉันหวังว่าจะทำมากกว่าวิธีการทั่วไป "วิธีทำ X กับ gem Y" และทำภาพรวมแบบกว้างๆ ของส่วนประกอบทั้งหมดที่รวมกันเพื่อสร้างแอปบรรทัดคำสั่งชั้นหนึ่งแทน
อินพุต:ตัวแปรสภาพแวดล้อม
โดยทั่วไปแล้ว ตัวแปรสภาพแวดล้อมมักใช้สำหรับค่าคอนฟิกูเรชันสั้นๆ ที่คุณต้องการเก็บไว้ชั่วขณะหนึ่ง คีย์ API เป็นตัวอย่างที่ดี
การตั้งค่าตัวแปรสภาพแวดล้อม
โดยปกติผู้ใช้ตั้งค่าตัวแปรสภาพแวดล้อมผ่านเชลล์เช่นทุบตี พวกเขาสามารถตั้งค่าสำหรับเซสชันเฉพาะ:
$ AWS_ACCESS_KEY_ID=FOO
พวกเขาสามารถตั้งค่าสำหรับเซสชันปัจจุบันรวมถึงเซสชัน bash ใหม่:
$ export AWS_ACCESS_KEY_ID=FOO
หรือจะตั้งไว้เป็นโปรแกรมเดียวก็ได้:
$ env AWS_ACCESS_KEY_ID=FOO aws s3 mb s3://mybucket
ตัวแปรสภาพแวดล้อมการอ่าน
ไม่ว่าผู้ใช้จะตั้งค่าตัวแปรสภาพแวดล้อมอย่างไร คุณสามารถอ่านได้ผ่าน ENV
. ของ Ruby แฮช.
key_id = ENV["AWS_ACCESS_KEY_ID"]
สภาพแวดล้อมทำงานอย่างไรภายใต้ประทุน
ทุกกระบวนการมีตารางตัวแปรสภาพแวดล้อมที่เกี่ยวข้อง เมื่อเกิดกระบวนการย่อย พวกเขาจะได้รับสำเนาของตารางนั้นพร้อมกับการเปลี่ยนแปลงที่ผู้ปกครองต้องการทำ
ใสเหมือนโคลน?
สำหรับคำอธิบายที่อ่านง่ายยิ่งขึ้น โปรดดูบล็อกโพสต์อื่นๆ ของฉัน:คู่มือ Rubyist's Guide to Environment Variables
อาร์กิวเมนต์บรรทัดคำสั่ง
อาร์กิวเมนต์บรรทัดคำสั่งคือสิ่งที่คุณใส่หลังชื่อโปรแกรมเมื่อคุณเรียกใช้โปรแกรมในเทอร์มินัล:
$ echo foo bar baz
ในตัวอย่างด้านบน "foo", "bar" และ "baz" เป็นอาร์กิวเมนต์บรรทัดคำสั่งทั้งหมด
ตัวอย่างที่สมจริงกว่านี้อาจมีลักษณะดังนี้:
$ honeybadger deploy -v --environment staging
แต่แม้กระทั่งที่นี่ อาร์กิวเมนต์บรรทัดคำสั่งก็เป็นเพียงข้อความ หากเราต้องการ --environment staging
ถึง หมายถึง เราต้องคิดออกเอง
การส่งอาร์กิวเมนต์บรรทัดคำสั่ง
เราได้เห็นการโต้แย้งที่ส่งมาในลักษณะนี้:
$ echo foo bar baz
แต่เนื่องจาก Ruby เป็นภาษาที่ตีความ คุณจึงอาจเจอสิ่งนี้:
$ ruby myprog.rb foo
เท่าที่เกี่ยวข้องกับระบบปฏิบัติการ คุณกำลังเรียกใช้โปรแกรมที่เรียกว่า "ruby" และส่งต่ออาร์กิวเมนต์สองข้อ
โชคดีที่ Ruby ฉลาดพอที่จะรู้ว่าอาร์กิวเมนต์ "foo" มีไว้สำหรับโปรแกรมของคุณ ไม่ใช่สำหรับตัว Ruby เอง ดังนั้นโปรแกรมของคุณจะเห็นอาร์กิวเมนต์เดียว "foo"
.
การอ่านอาร์กิวเมนต์บรรทัดคำสั่ง
อาร์กิวเมนต์บรรทัดคำสั่งถูกเก็บไว้ในอาร์เรย์ที่เรียกว่า ARGV
. เป็นตัวแปรสากล คุณจึงสามารถเข้าถึงได้จากทุกที่ในโปรแกรม
ในตัวอย่างด้านล่าง เรากำลังพิมพ์ทุกอย่างใน ARGV
. คุณสามารถคัดลอกและวางสิ่งนี้ลงในเทอร์มินัลแล้วลองใช้งานดูสักหน่อย
$ ruby -e "puts ARGV.inspect" foo bar baz
["foo", "bar", "baz"]
โปรดอย่าละทิ้ง
ruby -e
ธุรกิจ. ฉันใช้ที่นี่เท่านั้นเพราะมันทำให้ฉันแสดงโปรแกรมและผลลัพธ์ของการรันในบรรทัดเดียวได้
การแยกวิเคราะห์อาร์กิวเมนต์บรรทัดคำสั่ง
อาร์กิวเมนต์บรรทัดคำสั่งเป็นเพียงข้อความ หากคุณต้องการให้ข้อความหมายถึง อะไรก็ตาม คุณจะต้องแยกวิเคราะห์มัน โชคดีที่มีห้องสมุดดีๆ หลายแห่งที่สามารถช่วยคุณในการแยกวิเคราะห์ได้
ในช่วงหลายปีที่ผ่านมา ไวยากรณ์มาตรฐานเล็กน้อยสำหรับอาร์กิวเมนต์บรรทัดคำสั่งได้ปรากฏขึ้น หน้าตาประมาณนี้:
$ program -a --option foo
สไตล์นี้ช่วยให้คุณมีแฟล็กบูลีนเช่น -h
เพื่อแสดงความช่วยเหลือ นอกจากนี้ยังช่วยให้คุณระบุตัวเลือกด้วยค่าต่างๆ ได้อีกด้วย
แนะนำ OptionParser
OptionParser
คลาสเป็นส่วนหนึ่งของไลบรารีมาตรฐานของ Ruby ช่วยให้คุณแยกวิเคราะห์ตัวเลือกที่ตรงกับสไตล์ด้านบนได้ง่ายๆ
มาสร้างแอปพลิเคชั่นง่ายๆ ที่กล่าวสวัสดีกัน มันจะช่วยให้คุณระบุชื่อผ่านอาร์กิวเมนต์บรรทัดคำสั่ง
require 'optparse'
# This will hold the options we parse
options = {}
OptionParser.new do |parser|
# Whenever we see -n or --name, with an
# argument, save the argument.
parser.on("-n", "--name NAME", "The name of the person to greet.") do |v|
options[:name] = v
end
end.parse!
# Now we can use the options hash however we like.
puts "Hello #{ options[:name] }" if options[:name]
พอรันก็ใช้งานได้เลย:
$ ruby hello.rb --name Starr
Hello Starr
$ ruby hello.rb -n Starr
Hello Starr
การเพิ่มหน้าจอ "help"
การเพิ่มคุณสมบัติความช่วยเหลือนั้นง่ายเหมือนกัน สิ่งที่เราต้องทำคือใส่ข้อความและเพิ่มคำสั่งสำหรับ -h
:
OptionParser.new do |parser|
parser.banner = "Usage: hello.rb [options]"
parser.on("-h", "--help", "Show this help message") do ||
puts parser
end
...
end.parse!
ตอนนี้เราสามารถขอความช่วยเหลือได้:
$ ruby hello.rb -h
Usage: hello.rb [options]
-h, --help Show this help message
-n, --name NAME The name of the person to greet.
อาร์กิวเมนต์ Typecasting
อาร์กิวเมนต์บรรทัดคำสั่งทั้งหมดเป็นสตริง แต่บางครั้งคุณต้องการให้ผู้ใช้ระบุตัวเลขหรือวันที่ การแปลงด้วยมืออาจเป็นเรื่องที่น่าเบื่อ ดังนั้น OptionParser
ทำเพื่อคุณ
ในตัวอย่างด้านล่าง ฉันจะเพิ่มตัวเลือก "การนับ" ซึ่งให้ผู้ใช้ระบุจำนวนครั้งที่โปรแกรมควรทักทาย ฉันกำลังบอก OptionParser
เพื่อส่งเป็น Integer
ที่มันทำ
OptionParser.new do |parser|
...
# Note the `Integer` arg. That tells the parser to cast the value to an int.
# I could have used `Float`, `Date`, or a number of other types.
parser.on("-c", "--count COUNT", Integer, "Repeat the message COUNT times") do |v|
options[:count] = v
end
end.parse!
if options[:name]
options.fetch(:count, 1).times do
puts "Hello #{ options[:name] }"
end
end
ตอนนี้เมื่อฉันเปิดโปรแกรม ฉันจะได้รับคำทักทายหลายครั้ง:
$ ruby hello.rb -n Starr -c 5
Hello Starr
Hello Starr
Hello Starr
Hello Starr
Hello Starr
หลักการตั้งชื่อ
หนึ่งในแง่มุมที่ยากที่สุดของการเขียนโปรแกรมคือการหาว่าควรตั้งชื่ออะไร อาร์กิวเมนต์บรรทัดคำสั่งจะไม่มีข้อยกเว้น เพื่อช่วยคุณ ฉันได้รวบรวมตารางข้อโต้แย้งทั่วไปสั้นๆ และความหมาย:
ตั้งค่าสถานะ | ความหมายทั่วไป |
---|---|
-a | ทั้งหมด ต่อท้าย |
-d | โหมดดีบัก หรือระบุไดเร็กทอรี |
-e | ดำเนินการบางอย่าง หรือแก้ไข |
-f | ระบุไฟล์หรือบังคับการดำเนินการ |
-h | ช่วยเหลือ |
-m | ระบุข้อความ |
-o | ระบุไฟล์เอาต์พุตหรืออุปกรณ์ |
-q | โหมดเงียบ |
-v | โหมดละเอียด พิมพ์เวอร์ชันปัจจุบัน |
-y | ตอบทุกข้อความว่า "ใช่" |
ทางเลือกสำหรับ OptionParser
OptionParser
ดี แต่มีข้อจำกัด เห็นได้ชัดว่าไม่รองรับรูปแบบคำสั่งที่ได้รับความนิยมในช่วงไม่กี่ปีที่ผ่านมา:
$ myprog command subcommand -V
โชคดีที่มี ตัน ของตัวเลือกในการแยกวิเคราะห์ไลบรารี ฉันแน่ใจว่าคุณสามารถหาคนที่คุณชอบได้ สามสิ่งที่ดูน่าสนใจมีดังนี้:
- GLI - ตัวแยกวิเคราะห์บรรทัดคำสั่ง "Git-like Interface" ช่วยให้คุณสร้างแอปพลิเคชันที่เหมือนกับ git เป็นไฟล์สั่งการเดียวที่มีหลายคำสั่ง
- CRI - ไลบรารีที่ใช้งานง่ายสำหรับสร้างเครื่องมือบรรทัดคำสั่งพร้อมรองรับคำสั่งที่ซ้อนกัน
- Methadone - Methadone มีตัวเลือกการแยกวิเคราะห์ DSL ของตัวเอง แต่มันไปไกลกว่านั้น มันจะตั้งค่าโครงสร้างไดเร็กทอรีสำหรับคุณ ชุดทดสอบ และการบันทึก มันเหมือนกับ Rails สำหรับ CLI ของคุณ
การป้อนข้อมูลจำนวนมากขึ้นด้วย STDIN
อาร์กิวเมนต์บรรทัดคำสั่งไม่เหมาะสำหรับการป้อนข้อมูลจำนวนมาก คุณจะต้องใช้สตรีม IO และ STDIN ก็เป็นหนึ่งในสตรีม IO ที่สะดวกที่สุด
โปรแกรมส่วนใหญ่จะกำหนด STDIN โดยอัตโนมัติจากระบบปฏิบัติการ เหมือนกับไฟล์แบบอ่านอย่างเดียวที่ระบบปฏิบัติการใช้เพื่อส่งข้อมูลแอปของคุณ สามารถใช้เพื่อรับอินพุตคีย์บอร์ดจากผู้ใช้ แต่ที่สำคัญกว่านั้นคือใช้รับข้อมูลจากโปรแกรมอื่นผ่านท่อได้
คุณใช้ STDIN เหมือนกับที่คุณใช้ไฟล์แบบอ่านอย่างเดียว ที่นี่ฉันกำลังวางข้อความลงใน STDIN ของโปรแกรมของฉัน จากนั้นฉันก็ใช้ read
วิธีการดึงมัน
$ echo "hello world" | ruby -e "puts STDIN.read.upcase"
HELLO WORLD
ใน Ruby คุณสามารถใช้ Enumerable
คุณสมบัติบนวัตถุ IO และ STDIN ก็ไม่มีข้อยกเว้น นั่นหมายความว่าคุณสามารถเล่นกลได้ทุกประเภท:
# Get the first 20 lines
STDIN.first(20)
# Convert to integers and reject odds
STDIN.map(&:to_i).reject(&:odd)
...etc
กำลังส่งออกผลลัพธ์ไปยัง STDOUT
STDOUT เป็นสตรีม IO แบบเขียนอย่างเดียวที่ระบบปฏิบัติการกำหนดให้กับโปรแกรมของคุณ
โดยการเขียนถึง STDOUT คุณสามารถส่งข้อความไปยังเทอร์มินัลของผู้ใช้ได้ ผู้ใช้สามารถเปลี่ยนเส้นทางเอาต์พุตไปยังไฟล์หรือไปป์ไปยังโปรแกรมอื่นได้
คุณจะใช้ STDOUT เหมือนกับที่คุณใช้ไฟล์แบบเขียนอย่างเดียวอื่นๆ:
STDOUT.write("Hi!\n")
# hi
และแน่นอน puts
และ print
ทั้งส่งออกไปยัง STDOUT
กำลังส่งข้อมูลสถานะไปที่ STDERR
STDERR เป็นอีกหนึ่งสตรีม IO แบบเขียนอย่างเดียว ไม่ใช่สำหรับผลผลิตทั่วไป มีไว้สำหรับข้อความแสดงสถานะโดยเฉพาะ จึงไม่ขวางทาง ของจริง เอาท์พุท
โดยปกติแล้ว STDERR จะแสดงในเทอร์มินัลของผู้ใช้ แม้ว่าพวกเขาจะเปลี่ยนเส้นทาง STDOUT ไปยังไฟล์หรือโปรแกรมอื่น
ในตัวอย่างด้านล่าง เราใช้ curl เพื่อดึงหน้าเว็บ มันส่งออกเนื้อหาของหน้าเว็บไปยัง STDOUT และแสดงข้อมูลความคืบหน้าไปยัง STDERR:
$ curl "https://www.google.com/" > /tmp/g.html
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 151k 0 151k 0 0 277k 0 --:--:-- --:--:-- --:--:-- 277k
การเขียนถึง STDERR จากโปรแกรมของคุณเองนั้นง่ายมาก เพียงแค่ปฏิบัติต่อมันเหมือนวัตถุ IO มันคือ:
STDERR.write("blah\n")
STDERR.puts("blah")
ใช้ไม้สวยตีมัน
ตัวอย่างที่เราได้เห็นมาจนถึงตอนนี้จะไม่ได้รับรางวัลการออกแบบใดๆ แต่ไม่มีเหตุผลใดที่แอปบรรทัดคำสั่งควรน่าเกลียดหรือไม่โต้ตอบ
หากคุณตัดสินใจที่จะพัฒนาแอปของคุณ มีอัญมณีดีๆ มากมายที่จะช่วยยกระดับการทำงานของคุณให้หนักขึ้น สามสิ่งนี้ที่ฉันชอบ:
- highline - Highline เป็นห้องสมุดที่ยอดเยี่ยมที่ไม่ต้องเสียเวลารวบรวม ตรวจสอบ และพิมพ์ข้อมูลจากผู้ใช้
- command_line_reporter - ทำให้สร้างรายงานความคืบหน้า ASCII ได้ง่าย
- ระบายสี - ให้คุณเพิ่มรหัสสี ANSI เพื่อทำให้ข้อความเก่าที่น่าเบื่อของคุณเป็นสีได้อย่างง่ายดาย
ดูรายการที่ละเอียดยิ่งขึ้น
สถานะการออก
หากโปรแกรมของคุณออกโดยมีข้อผิดพลาด คุณควรบอกระบบปฏิบัติการโดยใช้รหัสออก นั่นคือสิ่งที่ทำให้ bash code แบบนี้ทำงานได้:
$ prog1 && prog2
หากคุณไม่คุ้นเคยกับ &&
. ของ bash โอเปอเรเตอร์ มันหมายถึง "เรียกใช้ prog2 ต่อเมื่อออกจาก prog1 สำเร็จเท่านั้น"
Ruby ออกด้วยโค้ด "success" โดยค่าเริ่มต้น และโค้ด "failure" เป็นข้อยกเว้น ทุกสิ่งทุกอย่างขึ้นอยู่กับคุณที่จะนำไปใช้ โชคดีที่มันง่าย:
# Any nonzero argument to `exit` counts as failure
exit(1)
สำหรับข้อมูลเพิ่มเติม โปรดดูที่โพสต์อื่นของฉัน วิธีออกจากโปรแกรม Ruby
การตั้งชื่อกระบวนการ
หากโปรแกรมบรรทัดคำสั่งของคุณจะทำงานชั่วขณะหนึ่ง สิ่งสำคัญคือผู้คนรู้ว่ามันคืออะไรเมื่อพวกเขาแสดงรายการกระบวนการของระบบ โดยปกติ ชื่อกระบวนการของโปรแกรม Ruby จะประกอบด้วยทุกสิ่งที่คุณพิมพ์เพื่อเรียกใช้โปรแกรม ดังนั้น หากคุณพิมพ์ ruby myapp -v
นั่นคือชื่อของกระบวนการ
หากโปรแกรมของคุณมีข้อโต้แย้งมากมาย สิ่งนี้จะไม่สามารถอ่านได้ ดังนั้นคุณอาจต้องการตั้งชื่อกระบวนการที่เป็นมิตรมากขึ้น คุณสามารถทำเช่นนี้ได้:
Process.setproctitle("My Awesome Command Line App")
หากคุณรู้สึกแปลกใหม่ คุณสามารถใช้ชื่อกระบวนการเพื่อให้ข้อมูลผู้ใช้เกี่ยวกับกระบวนการที่ทำในช่วงเวลาที่กำหนด
Process.setproctitle("Mail Sender: initializing")
init(...)
Process.setproctitle("Mail Sender: connecting")
connect(...)
Process.setproctitle("Mail Sender: sending")
send(...)
สำหรับข้อมูลเพิ่มเติม โปรดดูที่โพสต์ของฉัน:วิธีเปลี่ยนชื่อกระบวนการของสคริปต์ Ruby ตามที่แสดงด้านบนและ ps