构建自定义命令行工具 - 打造专属指令体

前言

在日常开发工作中,我们经常需要重复执行一些特定的操作,比如批量处理文件、自动化部署、数据处理等。如果能够构建一个属于自己的命令行工具,通过简短的指令快速执行这些任务,将大大提升工作效率。

本文将手把手教你如何从零开始构建一个可扩展的自定义命令行工具,实现自己的指令体系。

技术选型

  • 语言: Python 3.x (简单易用,跨平台)
  • 核心特性 :
    • 命令解析与分发
    • 插件化指令扩展
    • 参数处理
    • 友好的帮助信息

完整源码实现

主程序 mycli.py

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
自定义命令行工具 - MyCLI
作者: Your Name
日期: 2025-10-21
"""

import sys
import os
from typing import Dict, Callable, List
import json
from datetime import datetime


class CommandRegistry:
    """命令注册器 - 管理所有自定义指令"""
    
    def __init__(self):
        self.commands: Dict[str, Callable] = {}
        self.descriptions: Dict[str, str] = {}
    
    def register(self, name: str, description: str):
        """装饰器: 注册命令"""
        def decorator(func: Callable):
            self.commands[name] = func
            self.descriptions[name] = description
            return func
        return decorator
    
    def execute(self, command: str, args: List[str]):
        """执行命令"""
        if command in self.commands:
            try:
                return self.commands[command](args)
            except Exception as e:
                print(f"❌ 执行错误: {e}")
                return False
        else:
            print(f"❌ 未知命令: {command}")
            print("💡 使用 'help' 查看所有可用命令")
            return False
    
    def show_help(self):
        """显示帮助信息"""
        print("\n" + "="*50)
        print("🚀 MyCLI - 自定义命令行工具")
        print("="*50)
        print("\n📋 可用命令:\n")
        
        for name, desc in self.descriptions.items():
            print(f"  {name:<15} - {desc}")
        
        print("\n" + "="*50)
        print("💡 使用方法: mycli <命令> [参数...]")
        print("="*50 + "\n")


# 创建全局命令注册器
registry = CommandRegistry()


# ==================== 定义自定义命令 ====================

@registry.register("hello", "打招呼命令")
def cmd_hello(args: List[str]):
    """打招呼"""
    name = args[0] if args else "World"
    print(f"👋 Hello, {name}!")
    return True


@registry.register("time", "显示当前时间")
def cmd_time(args: List[str]):
    """显示当前时间"""
    now = datetime.now()
    print(f"⏰ 当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')}")
    return True


@registry.register("calc", "简单计算器 (支持: +, -, *, /)")
def cmd_calc(args: List[str]):
    """计算器"""
    if len(args) < 3:
        print("❌ 用法: calc <数字1> <操作符> <数字2>")
        print("   示例: calc 10 + 5")
        return False
    
    try:
        num1 = float(args[0])
        operator = args[1]
        num2 = float(args[2])
        
        operations = {
            '+': lambda x, y: x + y,
            '-': lambda x, y: x - y,
            '*': lambda x, y: x * y,
            '/': lambda x, y: x / y if y != 0 else None
        }
        
        if operator not in operations:
            print(f"❌ 不支持的操作符: {operator}")
            return False
        
        result = operations[operator](num1, num2)
        if result is None:
            print("❌ 错误: 除数不能为0")
            return False
        
        print(f"🧮 计算结果: {num1} {operator} {num2} = {result}")
        return True
        
    except ValueError:
        print("❌ 错误: 请输入有效的数字")
        return False


@registry.register("file", "文件操作 (list|create|delete)")
def cmd_file(args: List[str]):
    """文件操作"""
    if not args:
        print("❌ 用法: file <操作> [参数]")
        print("   操作: list, create <文件名>, delete <文件名>")
        return False
    
    operation = args[0]
    
    if operation == "list":
        print("📁 当前目录文件列表:")
        files = os.listdir('.')
        for i, file in enumerate(files, 1):
            icon = "📂" if os.path.isdir(file) else "📄"
            print(f"  {i}. {icon} {file}")
        return True
    
    elif operation == "create" and len(args) > 1:
        filename = args[1]
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(f"# 创建于 {datetime.now()}\n")
            print(f"✅ 文件已创建: {filename}")
            return True
        except Exception as e:
            print(f"❌ 创建失败: {e}")
            return False
    
    elif operation == "delete" and len(args) > 1:
        filename = args[1]
        try:
            if os.path.exists(filename):
                os.remove(filename)
                print(f"✅ 文件已删除: {filename}")
                return True
            else:
                print(f"❌ 文件不存在: {filename}")
                return False
        except Exception as e:
            print(f"❌ 删除失败: {e}")
            return False
    
    else:
        print("❌ 无效的操作")
        return False


@registry.register("note", "笔记管理 (add|list|clear)")
def cmd_note(args: List[str]):
    """简单笔记管理"""
    note_file = ".mycli_notes.json"
    
    # 加载笔记
    def load_notes():
        if os.path.exists(note_file):
            with open(note_file, 'r', encoding='utf-8') as f:
                return json.load(f)
        return []
    
    # 保存笔记
    def save_notes(notes):
        with open(note_file, 'w', encoding='utf-8') as f:
            json.dump(notes, f, ensure_ascii=False, indent=2)
    
    if not args:
        print("❌ 用法: note <操作> [内容]")
        print("   操作: add <内容>, list, clear")
        return False
    
    operation = args[0]
    
    if operation == "add" and len(args) > 1:
        notes = load_notes()
        content = ' '.join(args[1:])
        note_entry = {
            "id": len(notes) + 1,
            "content": content,
            "time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        }
        notes.append(note_entry)
        save_notes(notes)
        print(f"✅ 笔记已添加 (ID: {note_entry['id']})")
        return True
    
    elif operation == "list":
        notes = load_notes()
        if not notes:
            print("📝 暂无笔记")
        else:
            print(f"📝 共有 {len(notes)} 条笔记:\n")
            for note in notes:
                print(f"  [{note['id']}] {note['time']}")
                print(f"      {note['content']}\n")
        return True
    
    elif operation == "clear":
        if os.path.exists(note_file):
            os.remove(note_file)
        print("✅ 所有笔记已清空")
        return True
    
    else:
        print("❌ 无效的操作")
        return False


@registry.register("env", "显示环境变量")
def cmd_env(args: List[str]):
    """显示环境变量"""
    if args:
        # 显示特定环境变量
        var_name = args[0]
        value = os.environ.get(var_name)
        if value:
            print(f"🔧 {var_name} = {value}")
        else:
            print(f"❌ 环境变量不存在: {var_name}")
    else:
        # 显示所有环境变量
        print("🔧 环境变量列表:\n")
        for key, value in sorted(os.environ.items()):
            print(f"  {key} = {value}")
    return True


@registry.register("help", "显示帮助信息")
def cmd_help(args: List[str]):
    """显示帮助"""
    registry.show_help()
    return True


@registry.register("exit", "退出程序")
def cmd_exit(args: List[str]):
    """退出"""
    print("👋 再见!")
    sys.exit(0)


# ==================== 主程序 ====================

def main():
    """主函数"""
    print("\n🚀 欢迎使用 MyCLI!")
    print("💡 输入 'help' 查看所有命令,输入 'exit' 退出\n")
    
    # 交互模式
    if len(sys.argv) == 1:
        while True:
            try:
                user_input = input("MyCLI> ").strip()
                if not user_input:
                    continue
                
                parts = user_input.split()
                command = parts[0]
                args = parts[1:] if len(parts) > 1 else []
                
                registry.execute(command, args)
                
            except KeyboardInterrupt:
                print("\n\n👋 再见!")
                break
            except EOFError:
                print("\n\n👋 再见!")
                break
    
    # 命令行模式
    else:
        command = sys.argv[1]
        args = sys.argv[2:] if len(sys.argv) > 2 else []
        registry.execute(command, args)


if __name__ == "__main__":
    main()

实现步骤详解

步骤 1: 设计命令注册器

命令注册器是整个系统的核心,负责:

  • 注册命令与对应的处理函数
  • 存储命令描述信息
  • 分发和执行命令
python 复制代码
class CommandRegistry:
    def __init__(self):
        self.commands = {}      # 命令字典
        self.descriptions = {}  # 描述字典

步骤 2: 使用装饰器注册命令

通过装饰器模式,让命令注册变得优雅简洁:

python 复制代码
@registry.register("hello", "打招呼命令")
def cmd_hello(args):
    print(f"Hello, {args[0] if args else 'World'}!")

步骤 3: 实现命令执行逻辑

命令执行器负责:

  • 查找命令
  • 调用对应处理函数
  • 处理异常情况

步骤 4: 添加交互模式

支持两种运行模式:

  • 交互模式: 直接运行程序,进入命令提示符
  • 命令行模式 : python mycli.py <命令> [参数]

使用示例

1. 启动交互模式

bash 复制代码
python mycli.py

输出:

复制代码
🚀 欢迎使用 MyCLI!
💡 输入 'help' 查看所有命令,输入 'exit' 退出

MyCLI>

2. 查看帮助

bash 复制代码
MyCLI> help

3. 使用计算器

bash 复制代码
MyCLI> calc 100 + 50
🧮 计算结果: 100.0 + 50.0 = 150.0

4. 文件操作

bash 复制代码
MyCLI> file list
📁 当前目录文件列表:
  1. 📄 mycli.py
  2. 📂 docs

MyCLI> file create test.txt
✅ 文件已创建: test.txt

5. 笔记管理

bash 复制代码
MyCLI> note add 明天要完成项目文档
✅ 笔记已添加 (ID: 1)

MyCLI> note list
📝 共有 1 条笔记:

  [1] 2025-10-21 15:30:00
      明天要完成项目文档

6. 命令行模式

bash 复制代码
python mycli.py hello Python
👋 Hello, Python!

python mycli.py time
⏰ 当前时间: 2025-10-21 15:30:00

扩展建议

1. 添加配置文件支持

python 复制代码
import configparser

def load_config():
    config = configparser.ConfigParser()
    config.read('mycli.conf')
    return config

2. 添加日志功能

python 复制代码
import logging

logging.basicConfig(
    filename='mycli.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

3. 实现插件系统

python 复制代码
# plugins/my_plugin.py
@registry.register("mycommand", "我的自定义命令")
def my_command(args):
    print("这是我的插件命令!")

4. 添加命令别名

python 复制代码
class CommandRegistry:
    def __init__(self):
        self.aliases = {
            'h': 'help',
            'q': 'exit',
            'ls': 'file list'
        }

5. 参数验证

python 复制代码
from typing import List, Optional

def validate_args(args: List[str], 
                  min_args: int = 0, 
                  max_args: Optional[int] = None) -> bool:
    if len(args) < min_args:
        return False
    if max_args and len(args) > max_args:
        return False
    return True

项目优化

性能优化

  1. 懒加载: 只在使用时加载命令模块
  2. 缓存: 缓存频繁访问的数据
  3. 异步处理: 对于耗时操作使用异步

用户体验优化

  1. 命令补全: 集成 readline 库实现 Tab 补全
  2. 彩色输出: 使用 colorama 库美化输出
  3. 进度条: 使用 tqdm 显示长任务进度
python 复制代码
# 命令补全示例
import readline

def completer(text, state):
    options = [cmd for cmd in registry.commands.keys() 
               if cmd.startswith(text)]
    return options[state] if state < len(options) else None

readline.set_completer(completer)
readline.parse_and_bind('tab: complete')

部署与分发

1. 创建可执行脚本

bash 复制代码
chmod +x mycli.py

2. 添加到系统 PATH

bash 复制代码
# Linux/Mac
export PATH=$PATH:/path/to/mycli

# Windows
setx PATH "%PATH%;C:\path\to\mycli"

3. 创建 setup.py 打包

python 复制代码
from setuptools import setup

setup(
    name='mycli',
    version='1.0.0',
    py_modules=['mycli'],
    entry_points={
        'console_scripts': [
            'mycli=mycli:main',
        ],
    },
)

安装:

bash 复制代码
pip install -e .

总结

通过本文,我们完整实现了一个可扩展的自定义命令行工具。主要特点:

模块化设计 : 命令注册器 + 装饰器模式

易于扩展 : 添加新命令只需一个装饰器

双模式支持 : 交互模式 + 命令行模式

友好交互 : 清晰的提示和错误处理

实用功能: 计算器、文件操作、笔记管理等

这个框架可以根据你的需求无限扩展,打造属于自己的效率工具集!

相关推荐
东方佑3 小时前
从字符串中提取重复子串的Python算法解析
windows·python·算法
Dfreedom.4 小时前
一文掌握Python四大核心数据结构:变量、结构体、类与枚举
开发语言·数据结构·python·变量·数据类型
一半烟火以谋生4 小时前
Python + Pytest + Allure 自动化测试报告教程
开发语言·python·pytest
虚行4 小时前
C#上位机工程师技能清单文档
开发语言·c#
小羊在睡觉4 小时前
golang定时器
开发语言·后端·golang
CoderCodingNo4 小时前
【GESP】C++四级真题 luogu-B4068 [GESP202412 四级] Recamán
开发语言·c++·算法
叶子丶苏5 小时前
第八节_PySide6基本窗口控件_按钮类控件(QAbstractButton)
python·pyqt
Larry_Yanan5 小时前
QML学习笔记(四十四)QML与C++交互:对QML对象设置objectName
开发语言·c++·笔记·qt·学习·ui·交互
百锦再5 小时前
对前后端分离与前后端不分离(通常指服务端渲染)的架构进行全方位的对比分析
java·开发语言·python·架构·eclipse·php·maven