65.9K
CodeProject 正在变化。 阅读更多。
Home

命令行界面(CLI)框架

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2020年6月2日

GPL3

19分钟阅读

viewsIcon

39195

downloadIcon

861

自动化帮助、验证用户输入、运行脚本...

引言

命令行界面 (CLI) 无需过多介绍。它们是*nix系统的常用界面,MS-DOS(Windows的前身)曾完全使用CLI。尽管图形用户界面 (GUI) 已取得显著进展,但CLI仍被广泛使用,因为有些平台仅支持控制台应用程序。

Using the Code

我们将要介绍的CLI是Robust Services Core (RSC) 的CLI。如果您是第一次阅读有关RSC某方面内容的文章,请花几分钟阅读这篇序言

尽管RSC的存储库包含两个示例应用程序,但它们并非其核心目的。作为一个框架,RSC很少需要GUI。事实上,GUI是不合适的,因为需要GUI的应用程序通常会自己提供。其他应用程序可能针对甚至不支持GUI的平台。

如果您想基于RSC构建应用程序,本文将帮助您编写自己的CLI命令。如果您不想使用RSC但需要CLI,可以在RSC的GPL-3.0许可证下,复制和修改代码以满足您的需求。

功能

在深入研究代码之前,了解其目标很有帮助,因此让我们先看看RSC的CLI提供的功能。

。CLI可以从控制台或文件(“脚本”)获取输入,也可以将结果发送到控制台或文件。未来还将支持套接字输入和输出。

帮助。命令的语法是使用CLI基类定义的,这些基类强制命令及其每个参数提供基本文档。这使得所有命令都可以列出,并附有每个参数的描述。如果需要更详细的解释,可以显示更详细的帮助文件。

参数。CLI的基本参数是布尔值、字符、整数、指针和字符串。CLI基类会尽可能地过滤无效输入。每个参数可以是必需的或可选的,并且可以使用基本参数构造更专业的参数,例如文件名和IP地址。

分隔符。在同一行输入多个命令的能力很有用,因为用户不想等待前一个命令完成就输入下一个命令。分号 (;) 用于分隔同一行上的命令。

转义字符。既然一个字符 (;) 已经有了特殊含义,我们就需要一个转义字符,它可以放在字符前面,以抑制其特殊含义,并仅按字面意思使用。反斜杠 (\) 用于此目的。

字符串。默认情况下,命令行解析器获取字符串会跳过任何空格,然后将字符组合成字符串,直到遇到更多空格或行尾。因此,包含空格的字符串必须用引号 (") 括起来。

注释。CLI可以执行脚本(包含命令的文件),因此应该可以对其进行注释。斜杠 (/) 用于此目的;该行上它后面的任何内容都将被忽略。

默认值。每个命令按固定顺序接收其参数,必需参数必须排在可选参数之前。这与Unix的方法不同,Unix通过标记参数(例如,使用-p)来允许以灵活的顺序输入,并可以省略其中一些参数。由于固定顺序可能导致解析歧义,因此定义一个“跳过”字符来表示任何可选参数非常有用。这会导致使用该参数的默认值,以便可以输入下一个可选参数。波浪号 (~) 用于此目的。

符号。通过定义代表它们的符号,可以避免使用魔术常数。例如,在使用跟踪工具时,通常希望从跟踪中排除某些线程的工作。RSC根据线程执行工作的类型将其分配给一个派系。一个enum定义了这些派系,但与其在跟踪工具命令中使用其枚举量的整数值,不如定义像faction.systemfaction.audit这样的符号来表示它们。为了简化CLI命令的解析,这些符号通过在前面加上特殊字符来调用。安培符 (&) 用作前缀。

事务记录。将所有CLI输入和输出记录到单个事务文件中可能很有用,因此CLI支持此功能。

。RSC在静态库中实现,因此构建只需包含应用程序所需的那些功能。因此,CLI必须允许每个静态库添加自己的命令,并扩展它使用的其他库提供的命令。

中止。CLI应该能够中止正在进行的命令并提示用户输入新命令。这通常通过ctrl-C完成。

类概述

本文采用自下而上的方法。它从参数开始,然后是命令,接着是命令组,最后是实现CLI的线程。目标是为您提供添加自己的CLI命令所需的背景知识,同时涵盖CLI的许多功能。

CLI命令在系统初始化时创建,并且永不删除。创建CLI命令有一些样板代码,但我希望您会发现其回报是值得的。

CLI最初于2006年左右编写。虽然它经常演变以支持新功能,但从未被彻底重新审视。为了准备本文,我重写了其中的一部分,尽管其整体设计保持不变。但您仍可能发现一些令人尴尬的地方。我肯定在重新审视它时发现了!

CliParm

这是CLI参数的虚拟基类,因此它的构造函数定义了所有参数的共同点

CliParm::CliParm(c_string help, bool opt, c_string tag) :
   help_(help),
   opt_(opt),
   tag_(tag)
{
   Debug::Assert(help_ != nullptr);

   auto size = strlen(help_);
   auto total = ParmWidth + strlen(ParmExplPrefix) + size;

   if(size == 0)
      Debug::SwLog(CliParm_ctor, "help string empty", size);
   else if(total >= COUT_LENGTH_MAX)
      Debug::SwLog
         (CliParm_ctor, "help string too long", total - COUT_LENGTH_MAX + 1);
}
  • help,一个C风格字符串,是必需的,并且必须足够简洁,以便与描述参数合法值的列放在同一行。COUT_LENGTH_MAX的默认值是80
  • opt指定参数是必需的还是可选的。
  • tag是可选的;它允许在参数值前面加上tag=,类似于Unix使用其-p风格的前缀(前面已提及)。只有少数参数定义了标签,但这样做允许省略前面的可选参数。

CliBoolParm

这是布尔参数的基类。子类只需提供一个调用CliBoolParm构造函数来设置其自身属性的构造函数

CliBoolParm::CliBoolParm(c_string help, bool opt, c_string tag) : CliParm(help, opt, tag) { }

此构造函数仅接受CliParm已定义的参数。那么CliBoolParm为什么存在呢?答案是它解析布尔参数的输入。稍后我们将研究命令行是如何解析的。

CliCharParm

这是字符参数的基类,它接受来自指定列表的单个字符。子类只需提供一个调用CliCharParm构造函数来设置其自身属性的构造函数

CliCharParm::CliCharParm
   (c_string help, c_string chars, bool opt, c_string tag) :
   CliParm(help, opt, tag),
   chars_(chars)
{
   Debug::Assert(chars_ != nullptr);
}
  • chars是一个包含合法字符的字符串。

CliIntParm

这是整数参数的基类。子类只需提供一个调用CliIntParm构造函数来设置其自身属性的构造函数

CliIntParm::CliIntParm(c_string help, word min, word max, bool opt, c_string tag, bool hex) :
   CliParm(help, opt, tag),
   min_(min),
   max_(max),
   hex_(hex)
{
}
  • min是参数的最小合法值。
  • max是参数的最大合法值。
  • 如果参数必须以十六进制输入,则设置hex

CliPtrParm

这是指针参数的基类。子类只需提供一个调用CliPtrParm构造函数来设置其自身属性的构造函数

CliPtrParm::CliPtrParm(c_string help, bool opt, c_string tag) : CliParm(help, opt, tag) { }

CliBoolParm一样,不需要其他参数。任何高达uintptr_max的值都可以接受,并且必须以十六进制输入。

CliText

此类定义了一个特定字符串,该字符串可以是有效的参数。子类只需提供一个调用CliText构造函数来设置其自身属性的构造函数

CliText::CliText(c_string help, c_string text, bool opt, uint32_t size) :
   CliParm(help, opt, nullptr),
   text_(text)
{
   if(text_ == nullptr) text = EMPTY_STR;
   parms_.Init(size, CliParm::CellDiff(), MemImmutable);
}
  • text是可用作参数的特定字符串。
  • sizeparms_的最大大小,它包含可以跟在text后面的任何附加参数。
  • parms_的类型是Registry<CliParm>。RSC的Registry模板类似于vector,但允许元素指定其放置索引。注册表中的类可以通过其索引进行多态调用。parms_注册表使用MemImmutable,这意味着它在系统初始化后被写保护,非常类似于static const数据成员。

如果参数可以跟在字符串后面,构造函数也会创建那些参数。这方面的例子很快就会出现。

CliTextParm

这是一个字符串集合的容器,其中的任何一个字符串都是该参数的有效输入

CliTextParm::CliTextParm(c_string help, bool opt, uint32_t size,
   c_string tag) : CliParm(help, opt, tag)
{
   strings_.Init(size, CliParm::CellDiff(), MemImmutable);
}
  • sizestrings_的最大大小,其中包含该参数的合法字符串。
  • strings_的类型是Registry<CliText>。注册表中的最后一个字符串可能为空(请参阅CliText的构造函数),在这种情况下,它将匹配任何字符串。

CliTextParm子类为每个合法字符串定义一个索引,并将字符串与其索引进行注册。CLI的解析器将返回字符串的索引,这有效地允许在switch语句中使用该字符串。例如,以下类被CLI命令使用,这些命令允许用户通过输入onoff来启用或禁用配置参数

class SetHowParm : public CliTextParm
{
public:
   static const id_t On = 1;
   static const id_t Off = 2;

   SetHowParm() : CliTextParm("setting...")
   {
      BindText(*new CliText("on", "on"), On);
      BindText(*new CliText("off", "off"), Off);
   }
};

请注意,SetHowParm构造函数直接创建了两个CliText实例。如果您愿意,也可以通过将适当的参数传递给它们的构造函数来以这种方式创建CliBoolParmCliCharParmCliIntParmCliPtrParmCliTextCliTextParm的实例。然后,只有当参数需要其他参数或在多个命令中使用时,您才为参数定义实际的子类。

CliCommand

最后我们到了用于实现CLI命令的类。命令通过一个特定字符串调用,因此它派生自CliText,后面跟着命令通常需要的一些附加参数

CliCommand::CliCommand(c_string comm, c_string help, uint32_t size) :
   CliText(help, comm, false, size)
{
   if((comm != nullptr) && (strlen(comm) > CommandWidth))
   {
      Debug::SwLog(CliCommand_ctor, "command name length", strlen(comm));
   }
}

命令名称的长度受显示目的的限制,并且是为了避免烦恼用户。

CliCommand子类创建并注册跟在命令名称后面的参数。例如

DisplayCommand::DisplayCommand() :
   CliCommand("display", "Displays an object derived from NodeBase::Base.")
{
   BindParm(*new CliPtrParm("pointer to an object derived from Base"));
   BindParm(*new CliCharParm("'b'=brief 'v'=verbose (default='b')", "bv", true));
}

此命令调用大多数对象提供的Display函数。其参数是指向该对象的必需指针,以及一个可选字符,用于指定输出是简略还是详细。

CliCommand子类还重写了一个纯虚函数ProcessCommand,该函数从命令行获取其参数并执行命令。稍后我们将进行研究。

CliCommandSet

当一个命令的ProcessCommand函数非常大时,将其拆分成子命令,每个子命令都有自己的ProcessCommand函数,可以使事情更容易管理。因此CliCommandSet允许CliCommand实例直接注册到一个包含命令之下

CliCommandSet::CliCommandSet(c_string comm, c_string help, uint32_t size) :
   CliCommand(comm, help, size) { }

例如,NtIncrement.cpp包含用于测试各种RSC类的命令,并且每个类的测试命令被分组到一个CliCommandSet中。以下是测试NbHeap的命令集

HeapCommands::HeapCommands() :
   CliCommandSet("heap", "Tests an NbHeap function.")
{
   BindCommand(*new HeapCreateCommand);
   BindCommand(*new HeapDestroyCommand);
   BindCommand(*new HeapAllocCommand);
   BindCommand(*new HeapBlockToSizeCommand);
   BindCommand(*new HeapDisplayCommand);
   BindCommand(*new HeapFreeCommand);
   BindCommand(*new HeapValidateCommand);
}

这使得每个堆函数都可以通过其自己的ProcessCommand实现来执行。例如,

>heap alloc 256

最终调用HeapAllocCommand::ProcessCommand。基类CliCommandSet检索下一个参数("alloc")并使用它委托给第二个级别的命令。

CliIncrement

CliIncrement的子类包含给定静态库中可用的命令。目前,每个静态库只有一个增量,但如果需要,一个库可以包含多个。

RSC唯一的强制库是NodeBase命名空间实现的那个。所有其他库都是可选的,尽管有些库依赖于其他库。命令必须实现在可以使用命令所需的所有代码项的库中。尽管系统中的所有命令都可以注册到一个通用位置,无论它们是否被分组到增量中,这都可能在大型代码库中导致命令名称冲突。它还会显示许多与用户当前正在执行的操作无关的命令。

因此,当RSC初始化并提示用户输入时,其命令提示符是nb>。这表明只有NodeBase增量(NbIncrement.cpp)中的命令可用。要访问另一个增量中的命令,必须在命令前加上该增量的名称。但是,增量的名称也可以作为独立命令输入,这会将该增量推入可用增量的堆栈。这使得它的所有命令都可以无需前缀即可访问,任何名称冲突都将以堆栈顶部增量的优先级解决。

以下是输入nw增量(支持RSC的网络层),然后是sb增量(支持其会话处理层)的示例。quit命令将最顶层的增量从堆栈中移除并返回到前一个。请注意命令提示符如何更改以指示当前堆栈顶部的增量。输入两次quit后,我们就回到了nb增量

尽管CliIncrement不派生自CliParm,但其构造函数的参数与我们已经看到的参数相似

CliIncrement::CliIncrement(c_string name, c_string help, uint32_t size) :
   name_(name),
   help_(help)
{
   Debug::Assert(name_ != nullptr);
   Debug::Assert(help_ != nullptr);

   commands_.Init(size, CliParm::CellDiff(), MemImmutable);
   Singleton<CliRegistry>::Instance()->BindIncrement(*this);
}
  • name是增量的名称,就像每个命令都有名称一样。
  • help指示增量命令支持哪个静态库。
  • sizecommands_的最大大小,其中包含增量的所有命令。
  • commands_的类型是Registry<CliCommand>

构造函数将新增量注册到CliRegistry,后者包含系统中的所有增量。incrs命令列出了它们。

CliIncrement的子类只需提供一个构造函数来创建和注册其命令

NbIncrement::NbIncrement() : CliIncrement("nb", "NodeBase Increment", 48)
{
   BindCommand(*new HelpCommand);
   BindCommand(*new QuitCommand);
   BindCommand(*new IncrsCommand);
   // many more BindCommand() calls deleted
}

演练

获取帮助

在讨论如何实现CLI命令之前,让我们看看help命令的作用。不带任何参数输入此命令会向用户提供CLI的概述

这告诉我们如何列出nb增量中可用的命令。

让我们研究一下logs命令。

在这里,我们看到了SetHowParm的示例,它是前面显示的CliTextParm子类,它要求将字符串参数onoff作为命令的suppress选项的一部分。

上面的内容是从绑定到LogsCommand的参数自动生成的。命令及其参数构成一个解析树,也可以通过显示每个CLI对象必须提供的help参数来遍历该解析树以生成帮助文档。左侧列中的标点符号已在本文档开头出现的CLI通用帮助文档中进行了描述。

  • 缩进相同深度的参数属于上面第一个参数以及前一个深度的参数。
  • 括号 () 包围必需参数的替代选项。
  • 方括号 [] 包围可选参数的替代选项。
  • 竖线 | 分隔CliCharParm的替代选项。
  • 冒号 : 分隔CliIntParm的最小值和最大值。
  • <str> 表示任何符合参数描述的字符串都是可接受的。

当RSC启动时,控制台会出现以下内容

这些NET500NODE500的神秘术语是什么?logs命令会告诉我们。

RSC的help目录包含有关其日志、警报和静态分析警告的文档,以及对几个CLI主题的更详细帮助。它的文件被提供有关这些主题帮助的CLI命令使用。docs目录中的文件help.cli包含RSC所有CLI增量和命令的help输出。

实现一个命令

CliThread收到输入时,它会解析命令名称并调用实现该命令的函数,该函数将是纯虚函数CliCommand::ProcessCommand的重写。

virtual word CliCommand::ProcessCommand(CliThread& cli) const = 0;

ProcessCommand函数接收正在执行该命令的CliThread的引用,并返回一个word,它是intptr_ttypedef(一个与平台字大小匹配的int,无论是32位还是64位)。命令可以根据需要使用其返回码,但几乎所有命令在成功时都返回0或正值,在失败时返回负值。

因此,ProcessCommand函数可以直接访问两类内容:其基类(CliCommandCliTextCliParm)声明的函数,以及CliThread定义的函数。命令知道其命令行参数出现的顺序,并使用以下函数获取它们

  • 对于整数:GetIntParmGetIntParmRc
  • 对于布尔值:GetBoolParmGetBoolParmRc
  • 对于字符:GetCharParmGetCharParmRc
  • 对于指针;GetPtrParmGetPtrParmRc
  • 对于列表中的字符串:GetTextIndexGetTextIndexRc
  • 对于任意字符串:GetStringGetStringRc
  • 对于文件名:GetFileNameGetFileNameRc
  • 对于标识符:GetIdentifierGetIdentifierRc

所有这些都由CliParm声明,尽管它们被子类选择性地重写。每对中的第一个函数返回一个bool,用于查找必需参数。第二个函数返回enum CliParm::Rc,用于查找可选参数。它返回以下值之一:

  • Ok,当找到有效参数时
  • None,当未找到有效参数时(因此应使用参数的默认值)
  • Error,当发生错误时(例如,格式错误的输入,输入流失败)

每个Get…函数都有两个参数:传递给ProcessCommand函数的CliThread,以及对数据的引用(word&bool&char&void*&string&),当结果为Ok时,该数据将被更新。

CliThread知道调用了哪个命令,因此它初始化命令的解析树,该解析树包含已绑定到该命令的参数。当ProcessCommand调用Get…函数时,它实际上是在当前解析树元素上调用该函数。因此,如果它尝试读取类型与解析树中先前定义的类型不匹配的参数,则解析将失败。这确保了解析树与ProcessCommand函数中的逻辑匹配,并且自动生成的帮助文档是正确的。当参数被提取时,它会更新解析树中的位置,该位置由CliThreadcookie_成员跟踪。

除了Get…函数之外,ProcessCommand实现还经常使用这些CliThread成员

  • EndOfInput总是在解析完最后一个参数后调用。如果输入行仍然包含未解析的字符,它会输出错误消息并返回false
  • obuf是一个公共ostringstream成员,用于组装输出。当ProcessCommand返回到CliThread时,obuf将被写入实际的输出流(即控制台或文件)。
  • BoolPromptCharPromptIntPromptStrPrompt允许ProcessCommand函数在显示提示符(例如,“您确定要这样做吗(y|n)?”)后向用户查询输入。
  • Report用于返回命令的word结果,并将obuf更新为将写入输出流的string

让我们看看前面控制台图像中输入logs explain…时调用的代码。首先要指出的是,实现一个具有子命令(如explain)的命令(如logs)有两种方法

  1. logs定义一个CliCommandSet子类。为每个子命令定义一个CliCommand子类,并将它们绑定到第一个类。
  2. logs定义一个CliCommand子类。为子命令定义一个CliTextParm子类,该子类将包含其中每个子命令的CliText实例。CliCommand子类解析与子命令关联的字符串。这会返回字符串的索引,然后该索引用于switch语句,如前面所述。

选择哪种方法很大程度上取决于个人喜好。第一种方法会导致许多更短的ProcessCommand函数,而第二种方法则导致一个更长的ProcessCommand函数,其中包含一个switch语句。RSC的大多数子命令都采用第二种方式实现,而这在CliCommandSet被添加之前是*唯一*的方式。

logs命令使用第二种方式;除explain之外的子命令代码已被移除

class LogsCommand : public CliCommand
{
public:
   static const id_t ListIndex = 1;
   static const id_t ExplainIndex = 2;
   static const id_t SuppressIndex = 3;
   static const id_t ThrottleIndex = 4;
   static const id_t CountIndex = 5;
   static const id_t BuffersIndex = 6;
   static const id_t WriteIndex = 7;
   static const id_t FreeIndex = 8;
   static const id_t LastNbIndex = 8;

   LogsCommand();
protected:
   word ProcessSubcommand(CliThread& cli, id_t index) const override;
private:
   word ProcessCommand(CliThread& cli) const override;
};

class LogsExplainText : public CliText
{
public:
   LogsExplainText() : CliText("displays documentation for a log", "explain")
   {
      BindParm(*new CliTextParm("log group name", false, 0));
      BindParm(*new CliIntParm("log number", TroubleLog, Log::MaxId));
   }
};

class LogsAction : public CliTextParm
{
public:
   LogsAction() : CliTextParm("subcommand...")
   {
      //  BindText() calls for other subcommand strings deleted
      BindText(*new LogsExplainText, LogsCommand::ExplainIndex);
   }
}; 

LogsCommand::LogsCommand() :
   CliCommand("Interface to the log subsystem.", "logs")
{
   BindParm(*new LogsAction);
}

//  Sets GROUP and LOG to the log group and log identified by NAME and ID.
//  Returns false if GROUP or LOG cannot be found, updating EXPL with an
//  explanation.  If GROUP is found and ID is 0, sets LOG to nullptr and
//  returns true.
//
bool FindGroupAndLog
   (const string& name, word id, LogGroup*& group, Log*& log, string& expl)
{
   auto reg = Singleton<LogGroupRegistry>::Instance();
   group = reg->FindGroup(name);

   if(group == nullptr)
   {
      expl = NoLogGroupExpl;
      return false;
   }

   log = nullptr;
   if(id == 0) return true;

   log = group->FindLog(id);

   if(log == nullptr)
   {
      expl = NoLogExpl;
      return false;
   }

   return true;
}

word LogsCommand::ProcessCommand(CliThread& cli) const
{
   id_t index;
   if(!GetTextIndex(index, cli)) return -1;
   return ProcessSubcommand(cli, index);
}

word LogsCommand::ProcessSubcommand(CliThread& cli, id_t index) const
{
   word rc = 0;
   string name, expl, key, path;
   word id;
   Log* log;
   LogGroup* group;

   switch(index)  // case labels and code for other subcommands deleted
   {
   case ExplainIndex:
      if(!GetString(name, cli)) return -1;
      if(!GetIntParm(id, cli)) return -1;
      if(!cli.EndOfInput()) return -1;

      if(!FindGroupAndLog(name, id, group, log, expl))
         return cli.Report(-1, expl);

      key = group->Name() + std::to_string(id);
      path = Element::HelpPath() + PATH_SEPARATOR + "logs.txt";
      rc = cli.DisplayHelp(path, key);

      switch(rc)
      {
      case -1: return cli.Report(-1, "This log has not been documented.");
      case -2: return cli.Report(-2, "Failed to open file " + path);
      }
      break;
   }

   return rc;
}

除了前面提到的类和函数之外,此代码还使用了CliThread::DisplayHelp,它使用一个键从文件中检索帮助文本。

获取事务记录

通过CLI执行的所有命令以及所有控制台输出都会自动记录在一个名为console的文件中,该文件名后面跟着可执行文件启动的时间(例如,console200530-071350-959.txt)。这些事务记录偶尔有用,但大多数时候只是使输出目录混乱。

运行脚本

read命令从一个文件中执行CLI命令,该文件又可以包含其他read命令。此功能用于编写测试脚本和自动化一系列常用命令,例如定义RSC静态分析工具使用的代码目录的buildlib脚本。

CLI提供了一些主要由脚本使用的命令

  • send将CLI输出重定向到文件,这对于记录测试脚本的结果很有用。此命令可以嵌套,其中send prev将输出恢复到前一个文件,而send cout则弹出所有文件并将输出恢复到控制台。
  • echo将一个字符串写入当前输出流。
  • delay在执行脚本的下一个命令之前等待,这在测试脚本需要给系统时间响应前一个命令时很有用。
  • if…else有条件地执行命令。例如,测试脚本可以检查最近执行的CLI命令的结果,该结果保存在符号cli.result中。
if &cli.result >= 0 echo "passed" else echo "failed"

RSC的input目录包含许多脚本,几乎所有脚本都是用于测试的。

扩展命令

静态库可能希望向其使用的某个库中定义的命令添加功能。例如,NodeBase命名空间/库中的includeexclude命令允许用户指定应被跟踪工具捕获的线程的工作。NetworkBase命名空间/库扩展了这些命令,以便用户可以指定应被跟踪工具捕获事件的IP端口。这是通过让NwIncludeCommand派生自IncludeCommand来实现的。有关更多详细信息,请参阅NbIncrement.cppNwIncrement.cpp

实现CLI应用程序

CliAppData为CLI应用程序提供线程局部存储,这些应用程序的数据需要在CLI命令之间保持。应用程序通过继承CliAppData来添加其数据和函数。CliThread定义了一些函数,允许应用程序通过CliThread提供的unique_ptr来管理这些数据。例如,NtTestData使用此功能来支持测试脚本和记录哪些测试已通过或失败的数据库。

关注点

中止命令

当RSC线程上发生异常时,将调用其Recover函数的重写。CliThread总是选择恢复执行,以便它可以尝试继续它正在做的事情。但是,如果它收到一个中断信号(通常是ctrl-C),它会清除与正在进行的工作相关的数据,以便在重新输入后立即重新提示用户输入新命令。

bool CliThread::Recover()
{
   auto sig = GetSignal();
   auto reg = Singleton<PosixSignalRegistry>::Instance();

   if(reg->Attrs(sig).test(PosixSignal::Break))
   {
      //  On a break signal, remain in the current increment(s) but
      //  abort whatever work was in progress.
      //
      appsData_.clear();
      outFiles_.clear();
      stream_.reset();
      ibuf->Reset();
   }

   return true;
}

RSC如何处理ctrl-C在此讨论。

历史

  • 2020年6月2日:初始版本
© . All rights reserved.