命令行--help 输出、手动和交互执行

一、自定义命令解析类

1、先定义一个命令行解析类

class CommandLineParser:
    def __init__(self):
        self.arguments = {}
        self.valid_options = {
            "help", "mode", "interval", "export", "wave_data_export", "wave_set",
            "ip", "serial_path", "rate", "devid", "url", "topic", "user", "passw",
        }

    def parse(self, args):
        current_name = ""
        values = []
        invalid_options = []

        for arg in args:
            if arg.startswith("--"):
                if current_name != "" and values:
                    self.arguments[current_name] = values
                    values = []
                current_name = arg[2:]
            elif arg.startswith("-"):
                if current_name != "" and values:
                    self.arguments[current_name] = values
                    values = []
                current_name = arg[1:]
            else:
                values.append(arg)

        if current_name != "":
            self.arguments[current_name] = values

        for key in self.arguments.keys():
            if key not in self.valid_options:
                invalid_options.append(key)

        if invalid_options:
            raise ValueError(f"Invalid option(s): {' '.join(invalid_options)}")

    def contains(self, name):
        return name in self.arguments

    def get_help_message(self):
        script_name = sys.argv[0]
        return f"""
            Usage: python {script_name} [options]

            Options:
              --help                Show this help message and exit
              --mode                Choose connection mode (1 for MIB RS232, 2 for LAN)
              --interval            Data Transmission interval (1-5)
              --export              Data export option (1-4)
              --wave_set            Waveform data export priority option (0-12)
              --wave_data_export    Choose Waveform data export scale option (1-2)
              --ip                  Target IP address for LAN 
              --serial_path         Target Serial port path for MIB
              --rate                Serial baudrate for MIB
              --devid               Device ID/Name for export
              --url                 Data export URL (JSON or MQTT WebSocket)
              --topic               MQTT Topic
              --user                MQTT Username
              --passw               MQTT Password

            Examples:
            python {script_name} --mode 1 --interval 1 --export 1 --wave_set 0 --wave_data_export 1 --serial_path /dev/ttyS5 --rate 115200
            python {script_name} --mode 2 --interval 1 --export 1 --wave_set 0 --wave_data_export 1 --ip localhost
            """

2、定义一个获取交互的函数,命令参数没有就从交互中取

def get_input(parser, key, options=None, prompt="", default=None):
    if options is None:
        options = []
    if parser.arguments.get(key):
        return parser.arguments[key][0]
    else:
        if default is not None:
            return default
        if options:
            for option in options:
                print(option)
        return input(prompt)

3、使用get_input取值

ip_address_remote = get_input(parser, 'ip', prompt="Enter the target IP address of the monitor assigned by DHCP: ",
                                  default="localhost") 

4、程序入口

def main(args):
    parser = CommandLineParser()
    try:
        parser.parse(args)
        if parser.contains('help'):
            print(parser.get_help_message())
            return
    except ValueError as e:
        print(e)
        print(parser.get_help_message())
        return

    ser_connect_set = get_input(parser, 'mode', ["1. Connect via MIB RS232 port", "2. Connect via LAN port"],
                                "Choose connection mode (1-2): ", default='2')

    if ser_connect_set == "1":
        connect_via_MIB(args)
    elif ser_connect_set == "2":
        connect_via_lan(args)
    else:
        print("Invalid mode selected!")


if __name__ == '__main__':
    main(sys.argv[1:]) 

二、getopt

1、定义函数

def print_help():
    print("python {} ".format(__file__))
    print("python {} --db-type=mysql --db-host=192.168.1.205".format(__file__))
    print("python {} --db-type=mysql --db-host=192.168.1.205 --db-port=3310".format(__file__))
    print("python {} --db-type=oracle --db-host=192.168.1.205".format(__file__))
    print("python {} --db-type=oracle --db-host=192.168.1.205 --db-port=1522 --db-oracle-sid=orac".format(__file__))
def main(argv):
    try:
        options, args = getopt.getopt(argv, "h", ["help", "db-type=", "db-host=", "db-oracle-sid=", "db-port="])
    except getopt.GetoptError:
        sys.exit()
    db_type, db_host, db_oracle_sid, db_port = None, None, None, None
    for option, value in options:
        if option in ("-h", "--help"):
            print_help()
            sys.exit()
        if option in "--db-type":
            db_type = value
        if option in "--db-host":
            db_host = value
        if option in "--db-oracle-sid":
            db_oracle_sid = value
        if option in "--db-port":
            db_port = value
    if not db_type or db_type not in (DB_TYPE_MYSQL, DB_TYPE_ORACLE) or not db_host:
        print_help()
        sys.exit()

    check_backup_yaml_format(SERVICE_ROOT_PATH)
    check_backup_yaml_format(SERVICES_TEMPLATE_PATH)
    do_work(db_type, db_host, db_oracle_sid, db_port)


if __name__ == "__main__":
    main(sys.argv[1:])

2、关于getopt模块的介绍

1、基本语法  

getopt.getopt(args, shortopts, longopts=[])

# 如下
try:
    options, args = getopt.getopt(
        argv,
        "h",                    # 短选项:支持 -h
        ["help",                 # 长选项列表
         "db-type=",
         "db-host=",
         "db-oracle-sid=",
         "db-port="]
    )
except getopt.GetoptError:
    sys.exit()  
argv
要解析的参数列表
"h"
支持的短选项:-h(不需要等号,因为无值)
第三个参数
支持的长选项列表:<br>=表示该选项需要值(如--db-type=mysql

参数说明:

      print("python {} ".format(__file__))  把脚本路径插入到字符串中,相对路径下只有文件名

  args: 通常是 sys.argv[1:],即命令行参数列表(不包括脚本名)

  shortopts: 短选项字符串,每个字符代表一个选项

  longopts: 长选项列表

2、options, args = getopt.getopt 含义

options: 解析后的 (option, value) 元组列表

print(type(getopt.getopt(argv, "h")))
<class 'tuple'>

[('--db-type', 'mysql'), ('--db-host', '192.168.1.205')]

options, args = getopt.getopt(argv, "h", ["help", "db-type=", "db-host=", "db-oracle-sid=", "db-port="])

3、argv 应该是从命令行接收到的参数列表,通常是 sys.argv[1:]

  • python script.py --db-type=oracle --db-host=192.168.1.1 
  • sys.argv[1:] 会是 ['--db-type=oracle', '--db-host=192.168.1.1']

  • 把 sys.argv[1:] 传给 getopt.getopt()

  • 源码中定义了对args的处理,主要是取到选项和选项的值  def getopt(args, shortopts, longopts = []):

使用示例

import getopt
import sys


def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "ho:vf:dm:", [
            "help", "output=", "file=", "debug", "mode=", "name=", "version"
        ])
    except getopt.GetoptError as err:
        print(str(err))
        usage()
        sys.exit(2)

    output = None
    verbose = False
    input_file = None
    debug = False
    mode = "default"
    name = None

    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        elif o in ("-o", "--output"):
            output = a
            print(output)
        elif o == "-v":
            verbose = True
            print(verbose)
        elif o in ("-f", "--file"):
            input_file = a
            print(input_file)
        elif o in ("-d", "--debug"):
            debug = True
            print(debug)
        elif o in ("-m", "--mode"):
            mode = a
            print(mode)
        elif o == "--name":
            name = a
            print(name)
        elif o == "--version":
            print("Script Version 1.0")
            sys.exit()
        else:
            assert False, "unhandled option"


def usage():
    print("""Usage: script.py [OPTIONS]
Options:
  -h, --help            Show this help message and exit
  -o OUTPUT, --output=OUTPUT
                        Specify output file
  -v                    Verbose mode
  -f FILE, --file=FILE  Input file
  -d, --debug           Enable debug mode
  -m MODE, --mode=MODE  Specify operation mode
  --name=NAME           Specify name
  --version             Show version information
    """)


if __name__ == "__main__":
    main()

关键点:

  短选项格式:单个字母后跟冒号表示该选项需要参数,如 "ho:vf:dm:"

  • h: 不需要参数
  • o:: 需要参数
  • v: 不需要参数
  • f:: 需要参数
  • d: 不需要参数
  • m:: 需要参数
  • name这里没有短选项

  长选项格式:选项名后跟等号表示需要参数,如 ["help", "output="]

  getopt() 返回两个值:(options, args) 

    options 是一个 (option, value) 对的列表,列表套元组

    args 包含那些没有被 opts 处理的命令行参数

错误处理:
使用 try-except 块来捕获 getopt.GetoptError 异常,这样可以处理无效的命令行参数。

三、推荐使用的argparse和click

1、argparse

是Python标准库中最推荐使用的命令行解析模块。它功能强大,使用简单,并能自动生成帮助和使用信息。

def main():
    SCRIPT_NAME = os.path.basename(sys.argv[0])
    parser = argparse.ArgumentParser(
        description='EMR 文档压缩归档与黑名单删除工具',
        formatter_class=argparse.RawTextHelpFormatter
    )
    parser.add_argument('--op', choices=['compress', 'delete', 'extract'], required=True,
                        help='操作类型:compress(压缩归档)、delete(删除黑名单文档)、extract(从归档恢复)')
    compress_group = parser.add_argument_group('压缩归档参数')
    compress_group.add_argument('--years', metavar='N', type=int, default=6,
                                help=f"""归档距今超过 N 年的文档,默认 N=6。
示例:
  python {SCRIPT_NAME} --op compress
  python {SCRIPT_NAME} --op compress --years 7
""")
    delete_group = parser.add_argument_group('黑名单删除参数')
    delete_group.add_argument('--blacklist', metavar='PATH',
                              help=f"""可选:指定外部黑名单文件路径(默认使用内置关键字)。
示例:
  python {SCRIPT_NAME} --op delete
  python {SCRIPT_NAME} --op delete --blacklist /root/blacklist.txt
""")
    extract_group = parser.add_argument_group('归档恢复参数')
    extract_group.add_argument('--archive', metavar='PATH',
                               help=f"""指定归档文件路径(.tar.xz)进行恢复。
示例:
  python {SCRIPT_NAME} --op extract --archive /mnt/backup/minio/archive_file/2015/20150101.tar.xz
""")

    args = parser.parse_args()
    setup_logging(args.op)

    try:
        if args.op == 'compress':
            global ARGS
            ARGS = args  # 供 group_docs_by_date 中使用
            compress_all()
        elif args.op == 'delete':
            blacklist_patterns = load_blacklist(args.blacklist)
            delete_blacklisted_documents(blacklist_patterns)
        elif args.op == 'extract':
            if args.archive and os.path.exists(args.archive):
                extract_archive(args.archive)
            else:
                logging.error("归档路径无效或不存在,请指定有效的 .tar.xz 文件")
    except Exception as e:
        logging.exception("未处理的程序异常:" + str(e))
    finally:
        logging.info("操作完成")


if __name__ == '__main__':
    main()

补充:

长短选项:直接多加一个 'x', 即可

parser.add_argument('--years', '-y', type=int, default=6, metavar='', help='归档年限(默认前6年)')

提示信息:metavar

  • metavar='' 不提示
  • metavar 不写,默认提示选项的大写字母
  • metavar='xxx',提示xxx

必填选项:不写报错

required=True,

# python  test.py   --help 
usage: test.py [-h] --op {compress,delete} [--start START] [--end END] [--years] [--blacklist PATH]

必填里面的选项:

'--op', choices=['compress', 'delete'],

子命令写法

比如 Git 风格:git commit, git push

subparsers = parser.add_subparsers(dest='command')

compress_parser = subparsers.add_parser('compress', help='压缩数据')
compress_parser.add_argument('--start', required=True)
compress_parser.add_argument('--end', required=True)

delete_parser = subparsers.add_parser('delete', help='删除黑名单')
delete_parser.add_argument('--blacklist', required=True)

一个参数接受多个值

parser.add_argument('--files', nargs='+', help='文件路径列表')

版本信息

parser.add_argument('--version', action='version', version='%(prog)s 1.0')

parser = argparse.ArgumentParser(
    description='EMR compress and delete tool',
    epilog='欢迎反馈问题到 dev@example.com'
)

2、click

这是一个第三方库,设计得非常优雅和直观。它使用装饰器来定义命令行接口,非常适合构建复杂的命令行应用。

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
    for _ in range(count):
        click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    hello()

  

 

Auto Copied
posted @ 2024-07-10 10:54  凡人半睁眼  阅读(48)  评论(0)    收藏  举报