命令行工具(CLI)

  命令行工具(CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。

  通常认为,命令行工具(CLI)没有图形用户界面(GUI)那么方便用户操作。因为,命令行工具的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行工具要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行工具往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行工具。

  另外,命令行工具(CLI)应该是一个开箱即用的工具,不需要安装任何依赖。

  一些熟悉的CLI工具如下:

  1. dotnet cli

  2. vue cli

  3. angular cli

  4. aws cli

  5. azure cli

指令设计

  本文将使用.Net Core(版本3.1.102)编写一个CLI工具,实现配置管理以及条目(item)管理(调用WebApi实现),详情如下:

  

框架说明

  编写CLI使用的主要框架是CommandLineUtils,它主要有以下优势:

  1. 良好的语法设计

  2. 支持依赖注入

  3. 支持generic host

WebApi

  提供api让cli调用,实现条目(item)的增删改查:

[Route("api/items")]
[ApiController]
public class ItemsController : ControllerBase
{
private readonly IMemoryCache _cache;
private readonly string _key = "items"; public ItemsController(IMemoryCache memoryCache)
{
_cache = memoryCache;
} [HttpGet]
public IActionResult List()
{
var items = _cache.Get<List<Item>>(_key);
return Ok(items);
} [HttpGet("{id}")]
public IActionResult Get(string id)
{
var item = _cache.Get<List<Item>>(_key).FirstOrDefault(n => n.Id == id);
return Ok(item);
} [HttpPost]
public IActionResult Create(ItemForm form)
{
var items = _cache.Get<List<Item>>(_key) ?? new List<Item>(); var item = new Item
{
Id = Guid.NewGuid().ToString("N"),
Name = form.Name,
Age = form.Age
}; items.Add(item); _cache.Set(_key, items); return Ok(item);
} [HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var items = _cache.Get<List<Item>>(_key); var item = items?.SingleOrDefault(n => n.Id == id);
if (item == null)
{
return NotFound();
} items.Remove(item);
_cache.Set(_key, items); return Ok();
}
}

CLI

  1. Program - 函数入口

[HelpOption(Inherited = true)] //显示指令帮助,并且让子指令也继承此设置
[Command(Description = "A tool to communicate with web api"), //指令描述
Subcommand(typeof(ConfigCommand), typeof(ItemCommand))] //子指令
class Program
{
public static int Main(string[] args)
{
//配置依赖注入
var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(PhysicalConsole.Singleton);
serviceCollection.AddSingleton<IConfigService, ConfigService>();
serviceCollection.AddHttpClient<IItemClient, ItemClient>(); var services = serviceCollection.BuildServiceProvider(); var app = new CommandLineApplication<Program>();
app.Conventions
.UseDefaultConventions()
.UseConstructorInjection(services); var console = (IConsole)services.GetService(typeof(IConsole)); try
{
return app.Execute(args);
}
catch (UnrecognizedCommandParsingException ex) //处理未定义指令
{
console.WriteLine(ex.Message);
return -;
}
}

//指令逻辑
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.WriteLine("Please specify a command.");
app.ShowHelp();
return ;
}
}

  2. ConfigCommand和ItemCommand - 实现的功能比较简单,主要是指令描述以及指定对应的子指令

[Command("config", Description = "Manage config"),
Subcommand(typeof(GetCommand), typeof(SetCommand))]
public class ConfigCommand
{
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.Error.WriteLine("Please submit a sub command.");
app.ShowHelp();
return ;
}
} [Command("item", Description = "Manage item"),
Subcommand(typeof(CreateCommand), typeof(GetCommand), typeof(ListCommand), typeof(DeleteCommand))]
public class ItemCommand
{
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.Error.WriteLine("Please submit a sub command.");
app.ShowHelp();
return ;
}
}

  3. ConfigService - 配置管理的具体实现,主要是文件读写

public interface IConfigService
{
void Set(); Config Get();
} public class ConfigService: IConfigService
{
private readonly IConsole _console;
private readonly string _directoryName;
private readonly string _fileName; public ConfigService(IConsole console)
{
_console = console;
_directoryName = ".api-cli";
_fileName = "config.json";
} public void Set()
{
var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), _directoryName);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
} var config = new Config
{
//弹出交互框,让用户输入,设置默认值为http://localhost:5000/
Endpoint = Prompt.GetString("Specify the endpoint:", "http://localhost:5000/")
}; if (!config.Endpoint.EndsWith("/"))
{
config.Endpoint += "/";
} var filePath = Path.Combine(directory, _fileName); using (var outputFile = new StreamWriter(filePath, false, Encoding.UTF8))
{
outputFile.WriteLine(JsonConvert.SerializeObject(config, Formatting.Indented));
}
_console.WriteLine($"Config saved in {filePath}.");
} public Config Get()
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), _directoryName, _fileName); if (File.Exists(filePath))
{
var content = File.ReadAllText(filePath);
try
{
var config = JsonConvert.DeserializeObject<Config>(content);
return config;
}
catch
{
_console.WriteLine("The config is invalid, please use 'config set' command to reset one.");
}
}
else
{
_console.WriteLine("Config is not existed, please use 'config set' command to set one.");
} return null;
}
}

  4. ItemClient - 调用Web Api的具体实现,使用HttpClientFactory的方式

public interface IItemClient
{
Task<string> Create(ItemForm form); Task<string> Get(string id); Task<string> List(); Task<string> Delete(string id);
} public class ItemClient : IItemClient
{
public HttpClient Client { get; } public ItemClient(HttpClient client, IConfigService configService)
{
var config = configService.Get();
if (config == null)
{
return;
} client.BaseAddress = new Uri(config.Endpoint); Client = client;
} public async Task<string> Create(ItemForm form)
{
var content = new StringContent(JsonConvert.SerializeObject(form), Encoding.UTF8, "application/json");
var result = await Client.PostAsync("/api/items", content); if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var item = Deserialize<Item>(stream);
return $"Item created, info:{item}";
} return "Error occur, please again later.";
} public async Task<string> Get(string id)
{
var result = await Client.GetAsync($"/api/items/{id}"); if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var item = Deserialize<Item>(stream); var response = new StringBuilder();
response.AppendLine($"{"Id".PadRight(40, ' ')}{"Name".PadRight(20, ' ')}Age");
response.AppendLine($"{item.Id.PadRight(40, ' ')}{item.Name.PadRight(20, ' ')}{item.Age}");
return response.ToString();
} return "Error occur, please again later.";
} public async Task<string> List()
{
var result = await Client.GetAsync($"/api/items"); if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var items = Deserialize<List<Item>>(stream); var response = new StringBuilder();
response.AppendLine($"{"Id".PadRight(40, ' ')}{"Name".PadRight(20, ' ')}Age"); if (items != null && items.Count > )
{
foreach (var item in items)
{
response.AppendLine($"{item.Id.PadRight(40, ' ')}{item.Name.PadRight(20, ' ')}{item.Age}");
}
} return response.ToString();
} return "Error occur, please again later.";
} public async Task<string> Delete(string id)
{
var result = await Client.DeleteAsync($"/api/items/{id}"); if (result.IsSuccessStatusCode)
{
return $"Item {id} deleted.";
} if (result.StatusCode == HttpStatusCode.NotFound)
{
return $"Item {id} not found.";
} return "Error occur, please again later.";
} private static T Deserialize<T>(Stream stream)
{
using var reader = new JsonTextReader(new StreamReader(stream));
var serializer = new JsonSerializer();
return (T)serializer.Deserialize(reader, typeof(T));
}
}

如何发布

  在项目文件中设置发布程序的名称(AssemblyName):

  <PropertyGroup>
   <OutputType>Exe</OutputType>
   <TargetFramework>netcoreapp3.</TargetFramework>
   <AssemblyName>api-cli</AssemblyName>
  </PropertyGroup>

  进入控制台程序目录:

  cd src/NetCoreCLI

  发布Linux使用版本:

  dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true

  发布Windows使用版本:

  dotnet publish -c Release -r win-x64 /p:PublishSingleFile=true

  发布MAC使用版本:

    dotnet publish -c Release -r osx-x64 /p:PublishSingleFile=true

使用示例

  这里使用Linux作为示例环境。

  1. 以docker的方式启动web api

  

  2. 虚拟机上没有安装.net core的环境

  

  3. 把编译好的CLI工具拷贝到虚拟机上,授权并移动到PATH中(如果不移动,可以通过./api-cli的方式调用)

    sudo chmod +x api-cli #授权
sudo mv ./api-cli /usr/local/bin/api-cli #移动到PATH

  4. 设置配置文件:api-cli config set

  

  5. 查看配置文件:api-cli config get

  

  6. 创建条目:api-cli item create

  

  7. 条目列表:api-cli item list

  

  8. 获取条目:api-cli item get

  

  9. 删除条目:api-cli item delete

  

  10. 指令帮助:api-cli -h, api-cli config -h, api-cli item -h

  

  

  

  11. 错误指令:api-cli xxx

  

源码地址

  https://github.com/ErikXu/NetCoreCLI

参考资料

  https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#using-rids](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#using-rids

  https://medium.com/swlh/build-a-command-line-interface-cli-program-with-net-core-428c4c85221

使用.Net Core编写命令行工具(CLI)的更多相关文章

  1. commanderJs编写命令行工具(cli)

    前言: 最近需要做一个内部的node cli来独立构建流程,对整个命令行工具实现流程有了大致了解,下面来解释一下如何实现一个cli,和如何使用 commander 库.   新手误区: 在开始实现之前 ...

  2. 如何用Node编写命令行工具

    0. 命令行工具 当全局安装模块之后,我们可以在控制台下执行指定的命令来运行操作,如果npm一样.我把这样的模块称之为命令行工具模块(如理解有偏颇,欢迎指正) 1.用Node编写命令行工具 在Node ...

  3. vs for Mac中的启用Entity Framework Core .NET命令行工具

    在vs for Mac的工具菜单中已没有了Package Manager Console. 我们可以通过以下方法使用Entity Framework Core .NET命令行工具: 1.添加Nuget ...

  4. 一个小时学会用 Go 编写命令行工具

    前言 最近因为项目需要写了一段时间的 Go ,相对于 Java 来说语法简单同时又有着一些 Python 之类的语法糖,让人大呼"真香". 但现阶段相对来说还是 Python 写的 ...

  5. 如何用node编写命令行工具,附上一个ginit示例,并推荐好用的命令行工具

    原文 手把手教你写一个 Node.js CLI 强大的 Node.js 除了能写传统的 Web 应用,其实还有更广泛的用途.微服务.REST API.各种工具……甚至还能开发物联网和桌面应用.Java ...

  6. golang开发:类库篇(三)命令行工具cli的使用

    为什么要使用命令行 觉得这个问题不应该列出来,又觉得如果初次进行WEB开发的话,可能会觉得所有的东西都可以使用API去做,会觉得命令行没有必要. 其实,一个生产的项目命令行是绕不过去的.比如运营需要导 ...

  7. Flask内置命令行工具—CLI

    应用发现 flask命令在Flask库安装后可使用,使用前需要正确配置FLASK_APP环境变量以告知用户程序所在位置.不同平台设置方式有所不同. Unix Bash (Linux, Mac, etc ...

  8. nodejs 编写(添加时间戳)命令行工具 timestamp

    Nodejs除了编写服务器端程序还可以编写命令行工具,如gulp.js就是Nodejs编写的. 接下来我们来实现一个添加时间戳的命令: $ timestamp action https://www.n ...

  9. node命令行工具之实现项目工程自动初始化的标准流程

    一.目的 传统的前端项目初始流程一般是这样: 可以看出,传统的初始化步骤,花费的时间并不少.而且,人工操作的情况下,总有改漏的情况出现.这个缺点有时很致命. 甚至有马大哈,没有更新项目仓库地址,导致提 ...

随机推荐

  1. golang kafka clinet 内存泄露问题处理

    go 内存泄露 新版本服务跑上一天内存占用20g,显然是内存泄露 内存泄露的问题难在定位 技术上的定位 主要靠 pprof 生成统计文件 之前写web项目 基于net/http/pprof 可以看到运 ...

  2. JDK9新特性-改进进程管理 API

    Java 9 这个版本对进程管理方面的改进也是相当大的.在为数不多的几次 Java 项目中,有偶尔用到多线程,但对多进程和进程方面的了解还真是太少. 我想,大部分人应该跟我一样,在编程之外知道有进程的 ...

  3. gin源码剖析

    介绍 Gin 是一个 Golang 写的 web 框架,具有高性能的优点,基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务.官方地址:https:// ...

  4. pycharm全局搜索快捷键无反应

    原因:和搜狗输入法的快捷键冲突

  5. H5本地离线存储

    前言上一篇文件结尾,有同学问我本地存储图片方法,其实本地存储方式有很多,我们打开谷歌浏览器,查看源代码,在resources页签中,有web SQl ,indexedDB等等,我前面文章讲过Local ...

  6. python自动化工具

    公司有些业务不断的重复复制和黏贴实在让人头疼,于是乎考虑使用python自动的生成文件,并且替换文件中的一些内容,把需要复制和黏贴的内容制作成 模版,以后的开发工作可以根据模版来自动生成文件,自己以后 ...

  7. XX系统测试总结报告

    XX系统测试总结报告 1        引言 1.1  编写目的 编写该测试总结报告主要有以下几个目的 1.  通过对测试结果的分析,得到对软件质量的评价 2.   分析测试的过程,产品,资源,信息, ...

  8. WiFi产生电磁辐射或让人想去自杀

    随着互联网在生活中的地位越来越重要,WiFi作为一种无线连接方式给了用户极大的便捷,然而有一部分科学家提出WiFi产生的电磁反应会对人的健康受到影响.面对这种说法,我们一直以为是专家在危言耸听,但是如 ...

  9. idea激活教程(永久)支持2019 3.1 亲测

    此教程已支持最新2019.3版本 本教程适用Windows.Mac.Ubuntu等所有平台. 激活前准备工作 配置文件修改已经不在bin目录下直接修改,而是通过Idea修改 如果输入code一直弹出来 ...

  10. C++走向远洋——63(项目二2、两个成员的类模板)

    */ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.cpp * 作者:常轩 * 微信公众号:Worldhe ...