1. 程式人生 > 實用技巧 >一個簡單高效的命令解析程式碼

一個簡單高效的命令解析程式碼

命令解析其實應用到很多地方,常用的例如:

  1. 管理命令:GM(game manager),直播管理命令;一般是運營,管理人員,管理場景,管理房間等使用。
  2. 除錯命令:例如實時檢視程序資訊,機器資源使用情況,列印詳細日誌等等。
  3. 許可權命令:例如實時開通白名單/黑名單,給使用者分配臨時許可權等等。
  4. 排程命令:人為實時更改函式排程,程序排程,機器排程等,流量分發、流量限制等,服務升降級等等。

命令的應用場景,好處,無須多講。下面說說這個命令解析的格式,大致跟mysql client連線差不多:

mysql -h test.command.com -P 6379 -u root -p


當然,有一些更簡單的一些處理方法,例如:直接根據空格來進行引數分割。比如下面這個遊戲gm命令,增加型別為1的貨幣1000。

addmoney 1 1000


這樣看起來很簡單,很清晰。但是,如果操作這個命令,需要加上操作日誌呢?

addmoney 1 1000 "重啟 補償"


這時候以空格來分割就有問題了。因為日誌字串中存在空格。當然有些東西,你可以通過妥協來解決。再例如,需要增加一個新命令,減少貨幣。

submoney 1 1000


其實從貨幣的角度,增加,減少本質只是對同種事物的不同操作,很多時候,我們設計程式碼的時候,就會設計成

money 1 1 1000 // 增加 型別1 1000
money 2 1 1000 // 減小 型別1 1000


當然,這僅僅是增加、減少;還有貨幣轉換、貨幣清空、貨幣轉移等等。如果用同一個命令money,根據這種格式,擴充套件性就會顯得很無力。

所以,這裡實現了開頭所說的格式,雖然看起來相對並不太人性化。但是,相容性,擴充套件性,是好於空格分割的。

接下來,上程式碼,核心入口函式,就是parseArgs。

#include <iostream>
#include <sstream>
 
using namespace std;
struct ArgInfo
{
    void clear()
    {
        sOpt.clear();
        sVal.clear();
    }
 
    // 值能為空,鍵不能為空
    inline bool empty()
    {
        
return sOpt.empty(); } string sOpt; string sVal; }; enum { IN_VALUE = 1, AFTER_VALUE, }; #define LOG() cout inline void skipSpace(const char *sArgs, size_t lArgsLen, size_t &i) { for (; i < lArgsLen; ++i) { switch (sArgs[i]) { case ' ': case '\t': break; default: return; } } return; } bool parseOpt(const char *sArgs, size_t lArgsLen, size_t &i) { for (size_t lOld = i; i < lArgsLen; ++i) { switch (sArgs[i]) { case ' ': case '\t': case '\n': if (i == lOld) return false; return true; case '\'': case '\"': case '-': return false; default: break; } } return true; } bool parseVal(const char *sArgs, size_t lArgsLen, size_t &i) { for (size_t lOld = i; i < lArgsLen; ++i) { switch (sArgs[i]) { case ' ': case '\t': case '\n': if (i == lOld) return false; return true; case '-': return false; default: break; } } return true; } bool parseQuotesVal(const char *sArgs, size_t lArgsLen, size_t &i) { for (; i < lArgsLen; ++i) { switch (sArgs[i]) { case '\"': return true; case '\\': ++i; break; default: break; } } return false; } bool parseArgs(const string &sArgs, vector<ArgInfo> &vArgs) { if (sArgs.empty()) return true; ArgInfo tInfo; size_t i = 0; skipSpace(sArgs.c_str(), sArgs.size(), i); while (i < sArgs.size()) { size_t lEnd = i; const char c = sArgs[i]; if ( c == '-') { ++i; if (!parseOpt(sArgs.c_str(), sArgs.size(), ++lEnd)) { return false; } if (!tInfo.empty()) { vArgs.emplace_back(tInfo); tInfo.clear(); } tInfo.sOpt = string(sArgs, i, lEnd - i); } else { if (tInfo.sOpt.empty()) { return false; } if (c == '\"') { ++i; if (!parseQuotesVal(sArgs.c_str(), sArgs.size(), ++lEnd)) { return false; } } else { if (!parseVal(sArgs.c_str(), sArgs.size(), ++lEnd)) { return false; } } tInfo.sVal = string(sArgs, i, lEnd - i); vArgs.emplace_back(tInfo); tInfo.clear(); } skipSpace(sArgs.c_str(), sArgs.size(), ++lEnd); i = lEnd; } if (!tInfo.empty()) { vArgs.emplace_back(tInfo); } return true; } void unitTest(const vector<string> &vTest) { for (auto & sTest : vTest) { vector<ArgInfo> vRet; if (parseArgs(sTest, vRet)) { ostringstream oss; oss << " " << sTest << " => "; for (auto & tRet : vRet) { oss << " " << tRet.sOpt << "#" << tRet.sVal; } LOG() << oss.str() << endl; } else { LOG() << " Format error:" << sTest << endl; } } } void testRightFormat() { vector<string> vTest = { "", "-abc", "-a 123 -b abc", "-a -b 123abc", "-a 123abc -b", "-a \"12'ab\" -b \"cd 34\"", "-a -b \"123abc\"", "-a \"123abc\" -b", "-a \"123-abc\" -b", "-a \"\\\"123-abc\\\"\" -b" }; unitTest(vTest); } void testWrongFormat() { vector<string> vTest = { "a", "- a", "-a- 123 -b abc", "--a -b 123abc", "-\"a 123abc -b", "-\"a\" 123abc -b", "-a \"12'ab -b \"cd 34\"", "-a \"123abc\" -b ab cd", "-a \"123abc\" -b ab-cd", "-a 123 -b \"ab\"c" }; unitTest(vTest); } int main() { testRightFormat(); //testWrongFormat(); return 0; }