命令行界面(CLI)框架
自动化帮助、验证用户输入、运行脚本...
引言
命令行界面 (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.system
和faction.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
是可用作参数的特定字符串。size
是parms_
的最大大小,它包含可以跟在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);
}
size
是strings_
的最大大小,其中包含该参数的合法字符串。strings_
的类型是Registry<CliText>
。注册表中的最后一个字符串可能为空(请参阅CliText
的构造函数),在这种情况下,它将匹配任何字符串。
CliTextParm
子类为每个合法字符串定义一个索引,并将字符串与其索引进行注册。CLI的解析器将返回字符串的索引,这有效地允许在switch
语句中使用该字符串。例如,以下类被CLI命令使用,这些命令允许用户通过输入on
或off
来启用或禁用配置参数
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
实例。如果您愿意,也可以通过将适当的参数传递给它们的构造函数来以这种方式创建CliBoolParm
、CliCharParm
、CliIntParm
、CliPtrParm
、CliText
和CliTextParm
的实例。然后,只有当参数需要其他参数或在多个命令中使用时,您才为参数定义实际的子类。
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
指示增量命令支持哪个静态库。size
是commands_
的最大大小,其中包含增量的所有命令。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
子类,它要求将字符串参数on
或off
作为命令的suppress
选项的一部分。
上面的内容是从绑定到LogsCommand
的参数自动生成的。命令及其参数构成一个解析树,也可以通过显示每个CLI对象必须提供的help
参数来遍历该解析树以生成帮助文档。左侧列中的标点符号已在本文档开头出现的CLI通用帮助文档中进行了描述。
- 缩进相同深度的参数属于上面第一个参数以及前一个深度的参数。
- 括号
(
…)
包围必需参数的替代选项。 - 方括号
[
…]
包围可选参数的替代选项。 - 竖线
|
分隔CliCharParm
的替代选项。 - 冒号
:
分隔CliIntParm
的最小值和最大值。 <str>
表示任何符合参数描述的字符串都是可接受的。
当RSC启动时,控制台会出现以下内容
这些NET500
和NODE500
的神秘术语是什么?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_t
的typedef
(一个与平台字大小匹配的int
,无论是32位还是64位)。命令可以根据需要使用其返回码,但几乎所有命令在成功时都返回0或正值,在失败时返回负值。
因此,ProcessCommand
函数可以直接访问两类内容:其基类(CliCommand
、CliText
和CliParm
)声明的函数,以及CliThread
定义的函数。命令知道其命令行参数出现的顺序,并使用以下函数获取它们
- 对于整数:
GetIntParm
或GetIntParmRc
- 对于布尔值:
GetBoolParm
或GetBoolParmRc
- 对于字符:
GetCharParm
或GetCharParmRc
- 对于指针;
GetPtrParm
或GetPtrParmRc
- 对于列表中的字符串:
GetTextIndex
或GetTextIndexRc
- 对于任意字符串:
GetString
或GetStringRc
- 对于文件名:
GetFileName
或GetFileNameRc
- 对于标识符:
GetIdentifier
或GetIdentifierRc
所有这些都由CliParm
声明,尽管它们被子类选择性地重写。每对中的第一个函数返回一个bool
,用于查找必需参数。第二个函数返回enum CliParm::Rc
,用于查找可选参数。它返回以下值之一:
Ok
,当找到有效参数时None
,当未找到有效参数时(因此应使用参数的默认值)Error
,当发生错误时(例如,格式错误的输入,输入流失败)
每个Get…
函数都有两个参数:传递给ProcessCommand
函数的CliThread
,以及对数据的引用(word&
、bool&
、char&
、void*&
或string&
),当结果为Ok
时,该数据将被更新。
CliThread
知道调用了哪个命令,因此它初始化命令的解析树,该解析树包含已绑定到该命令的参数。当ProcessCommand
调用Get…
函数时,它实际上是在当前解析树元素上调用该函数。因此,如果它尝试读取类型与解析树中先前定义的类型不匹配的参数,则解析将失败。这确保了解析树与ProcessCommand
函数中的逻辑匹配,并且自动生成的帮助文档是正确的。当参数被提取时,它会更新解析树中的位置,该位置由CliThread
的cookie_
成员跟踪。
除了Get…
函数之外,ProcessCommand
实现还经常使用这些CliThread
成员
EndOfInput
总是在解析完最后一个参数后调用。如果输入行仍然包含未解析的字符,它会输出错误消息并返回false
。obuf
是一个公共ostringstream
成员,用于组装输出。当ProcessCommand
返回到CliThread
时,obuf
将被写入实际的输出流(即控制台或文件)。BoolPrompt
、CharPrompt
、IntPrompt
和StrPrompt
允许ProcessCommand
函数在显示提示符(例如,“您确定要这样做吗(y|n)?”)后向用户查询输入。Report
用于返回命令的word
结果,并将obuf
更新为将写入输出流的string
。
让我们看看前面控制台图像中输入logs
explain…
时调用的代码。首先要指出的是,实现一个具有子命令(如explain
)的命令(如logs
)有两种方法
- 为
logs
定义一个CliCommandSet
子类。为每个子命令定义一个CliCommand
子类,并将它们绑定到第一个类。 - 为
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
命名空间/库中的include
和exclude
命令允许用户指定应被跟踪工具捕获的线程的工作。NetworkBase
命名空间/库扩展了这些命令,以便用户可以指定应被跟踪工具捕获事件的IP端口。这是通过让NwIncludeCommand
派生自IncludeCommand
来实现的。有关更多详细信息,请参阅NbIncrement.cpp和NwIncrement.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日:初始版本