用 OptionParser 构建 Command Line 工具

本文介绍如何使用 Ruby 的 OptionParser 库创建命令行工具。详细解释了 OptionParser 的基本用法,包括如何定义命令行选项、参数和行为,并提供了一个实际的例子。

用OptionParser创建命令行工具

下面这张图就是Ruby给出的OptionParser的文档,除了这张图片之外就是一个官方范例,然后就没了… 说实话我第一眼看了这张图和官方范例后感觉看不懂,需要反复通过Google各种文章和范例,才了解到了OptionParser的基本用法。

+--------------+
| OptionParser |<>-----+
+--------------+       |                      +--------+
                       |                    ,-| Switch |
     on_head -------->+---------------+    /  +--------+
     accept/reject -->| List          |<|>-
                      |               |<|>-  +----------+
     on ------------->+---------------+    `-| argument |
                        :           :        |  class   |
                      +---------------+      |==========|
     on_tail -------->|               |      |pattern   |
                      +---------------+      |----------|
OptionParser.accept ->| DefaultList   |      |converter |
             reject   |(shared between|      +----------+
                      | all instances)|
                      +---------------+

通常的Unix命令行参数包含下面这些形式:

  • Option - Option主要功能是用于调整命令行工具的行为,Option的表现通常有两种形式,short option或者long option。Option的类型有两种,switchflagswitch不带argument,而flag带有argument。
  • Argument - Argument通常表示命令行工具要操作的对象,通常是路径,URL或者名称等等。
  • Action - 表示命令行工具的行为,比如git命令的push或者pull等等。

举个例子git log --max-count=10git是command。log是action,表示查看git的提交历史。--max-count就是option,表示最多显示N条commit记录。而最后的=10就是argument,表示option的数值,即查看最后10条历史提交记录。所有的Unix命令行工具都遵循这样的一个约定,这里需要主意一下,Argument前面的=在很多命令行工具中是可以省略的。

OptionParser创建一个简单的命令行工具,通常我们只需要创建一个OptionParser的实例instance,然后给这个instance传入一个block,在这个block内部我们就可以使用OptionParser提供的方法来解析命令行参数,特别是用on方法来根据定义捕捉各种参数,并将参数解析成可被使用的Ruby数据,如String,Boolean,Array以及Hash等。而on方法最让人困惑的地方就是它异常灵活参数处理,比如on方法的第一个参数,如果是一个-加一个非空格字符,则把这个参数当作switch来处理,例如on('-n'),如果是一个-开头的字符,后面跟着一个空格外加另外一个字符,那么就把这个参数当作flag处理,例如on('-n NAME')。如果on方法的参数超过两个,并且两个都是String,那么则视这两个参数表示一个意思,例如on('-n NAME', '--name NAME')。如此这般的例子还有很多,如果有更高需求的朋友,我建议你还是直接去啃源代码吧。

下面我创建一个名为my_awesome_command.rb的命令行工具,这个工具直接输出我的命令行参数解析的结果,我用中文注释来说明OptionParser是怎么用的:

#!/usr/bin/env ruby

require 'optparse'

options = {}
option_parser = OptionParser.new do |opts|
  # 这里是这个命令行工具的帮助信息
  opts.banner = 'here is help messages of the command line tool.'

  # Option 作为switch,不带argument,用于将switch设置成true或false
  options[:switch] = false
  # 下面第一项是Short option(没有可以直接在引号间留空),第二项是Long option,第三项是对Option的描述
  opts.on('-s', '--switch', 'Set options as switch') do
    # 这个部分就是使用这个Option后执行的代码
    options[:switch] = true
  end

  # Option 作为flag,带argument,用于将argument作为数值解析,比如"name"信息
  #下面的“value”就是用户使用时输入的argument
  opts.on('-n NAME', '--name Name', 'Pass-in single name') do |value|
    options[:name] = value
  end

  # Option 作为flag,带一组用逗号分割的arguments,用于将arguments作为数组解析
  opts.on('-a A,B', '--array A,B', Array, 'List of arguments') do |value|
    options[:array] = value
  end
end.parse!

puts options.inspect

执行结果

$ ruby my_awesome_command.rb -h
here is help messages of the command line tool.
    -s, --switch                     Set options as switch
    -n, --name Name                  Pass-in single name
    -a, --array A,B                  List of arguments

$ ruby my_awesome_command.rb -s
{:switch=>true}

$ ruby my_awesome_command.rb -n Daniel
{:switch=>false, :name=>"Daniel"}

$ ruby my_awesome_command.rb -a Foo,Bar
{:switch=>false, :array=>["Foo", "Bar"]}

import os import re import random import getpass import socket import time import sys import stat import signal import threading import shutil import resource import math import subprocess from optparse import OptionParser # TODO set some global control parameter glbUserName = "" glbWorkPath = "" glbBlockPath = "" glbBlockName = "" compileStatus = 0 reapedCount = 0 curLiveCount = 0 reapedFlag = False bsubBusy = 0 # TODO Set enviroment color frontGroundColor = { "ANSI_BLACK" : 30, "ANSI_RED" : 31, "ANSI_GREED" : 32, "ANSI_YELLOW": 33, "ANSI_BLUE" : 34, "ANSI_PURPLE": 35, "ANSI_CYAN" : 36, "ANSI_WHITE" : 37 } # TODO define every print message by every color class setEnvMsg(): def __init__(self,message): self.msgContext = message self.fileError = "err.log" def writeMessage(self,message): self.msgContext = message def blackPrint(self,message): print('\033[1;30m' + message + '\033[0m') def redPrint(self,message): print('\033[1;31m' + message + '\033[0m') def greedPrint(self,message): print('\033[1;32m' + message + '\033[0m') def yellowPrint(self,message): print('\033[1;33m' + message + '\033[0m') def bluePrint(self,message): print('\033[1;34m' + message + '\033[0m') def purplePrint(self,message): print('\033[1;35m' + message + '\033[0m') def cyanPrint(self,message): print('\033[1;36m' + message + '\033[0m') def msgPrint(self,message,fg=frontGroundColor["ANSI_BLACK"]): if fg == frontGroundColor["ANSI_BLACK"]: self.blackPrint(message) elif fg == frontGroundColor["ANSI_RED"]: self.redPrint(message) elif fg == frontGroundColor["ANSI_GREED"]: self.greedPrint(message) elif fg == frontGroundColor["ANSI_YELLOW"]: self.yellowPrint(message) elif fg == frontGroundColor["ANSI_BLUE"]: self.bluePrint(message) elif fg == frontGroundColor["ANSI_PURPLE"]: self.purplePrint(message) elif fg == frontGroundColor["ANSI_CYAN"]: self.cyanPrint(message) def putBannerPrint(self,fgh=frontGroundColor["ANSI_RED"],fgb=frontGroundColor["ANSI_RED"]): self.msgPrint("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-",fgh) self.msgPrint("->\t" + self.msgContext,fgb) self.msgPrint("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-",fgh) def fatalMsgPrint(self,message): self.msgPrint("(FATAL:) " + message, frontGroundColor["ANSI_RED"]) sys.exit(1) def errorMsgPrint(self,message): self.msgPrint("(ERROR:) " + message, frontGroundColor["ANSI_YELLOW"]) def warnMsgPrint(self,message): self.msgPrint("(WARNING:) " + message, frontGroundColor["ANSI_BLUE"]) def infoMsgPrint(self,message): self.msgPrint("(INFO:) " + message, frontGroundColor["ANSI_BLACK"]) def debugMsgPrint(self,message): self.msgPrint("(DEBUG:) " + message, frontGroundColor["ANSI_BLACK"]) def errorMsgWrite(self,fileHandle,message): if fileHandle: fileHandle.write(message) def closeErrorFile(self,fileHandle): if fileHandle: fileHandle.close() # TODO Scripts to submit task to openlava class bsubTaskProcess(): def __init__(self): self.writeBusy = 0 self.readBusy = 0 self.msgCtrlObj = setEnvMsg("Bsub to manage task submit and post process") def excuteBsubCmd(self,command,bsubJobList,processPath): global bsubBusy while True: if bsubBusy==0: break; time.sleep(1) bsubBusy = 1 jobInfo = "Job <(\d+)> is submitted" if os.path.exists(processPath)==True: os.chdir(processPath) else: self.msgCtrlObj.fatalMsgPrint("No found the path: %0s"%(processPath)) p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal_newlines=True) time.sleep(1) while True: p.poll() line=p.stdout.readline() jobObj = re.search(jobInfo,line) if jobObj: jobId = jobObj.group(1) while self.readBusy==1: time.sleep(1) while self.writeBusy==1: time.sleep(1) if self.writeBusy==0: break self.writeBusy = 1 time.sleep(1) bsubJobList.append(jobId) self.writeBusy = 0 break else: jobId = None break p.wait() if p.returncode!=0: self.msgCtrlObj.fatalMsgPrint("Error occurred(%s) from "%(p.returncode) + command) bsubBusy = 0 return jobId def checkBsubJob(self,jobId): bsubJobStatus = 0 jobIdQueue = [] jobInfoList = [] if jobId==None: return p = subprocess.Popen(options.bJobNameCtrl+" -noheader", shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal_newlines=True) context=p.stdout.read() if "No unfinished job" not in context: for line in context.splitlines(): jobInfoList = line.split() jobIdQueue.append(jobInfoList[0]) if jobId in jobIdQueue: bsubJobStatus = 1 return bsubJobStatus def waitBsubCmdDone(self,jobId,bsubJobList): jobDoneCount = 3 if jobId==None: return time.sleep(10) while True: jobDoneStatus = 0 jobDoneIdx = 0 jobIdQueue = [] jobInfoList = [] time.sleep(5) p = subprocess.Popen(options.bJobNameCtrl+" -noheader", shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal_newlines=True) context=p.stdout.read() if "No unfinished job" not in context: for line in context.splitlines(): jobInfoList = line.split() jobIdQueue.append(jobInfoList[0]) else: return if jobId not in jobIdQueue: while True: time.sleep(1) jobDoneStatus = self.checkBsubJob(jobId) jobDoneIdx += 1 if jobDoneIdx==jobDoneCount: break if jobDoneStatus: continue while self.writeBusy==1: time.sleep(1) while self.readBusy==1: time.sleep(1) if self.readBusy==0: break self.readBusy = 1 time.sleep(1) if jobId in bsubJobList: bsubJobList.remove(jobId) self.readBusy = 0 break
06-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值