在编写可运行程序时,经常需要输入除了可运行文件之外的其它的命令行参数,可以用传统的getopt函数来分析,本文基于面向对象,分析一种管理命令行参数方法 -- 来源于webrtc项目,在阅读过程中,大家分享一下。
一,传统命令行分析
包含头文件:#include<unistd.h> int getopt(int argc,char * const argv[ ],const char * optstring); extern char *optarg; extern int optind, opterr, optopt;
假设命令行的输入格式的规则如下:
2.1 参数的值封装---FlagValue
这个类对参数的值进行封装,如--prefix=/usr,作为一个命令行参数时,prefix为键,/usr为值。在参数中,在此定义值的类型为布尔、整型、浮点、字符串中的一种。
由于一个值在只能取四种的一种,所以此处用联合类型表示FlagValue。
union FlagValue { static FlagValue New_BOOL(int b) { FlagValue v; v.b = (b != 0); return v; } static FlagValue New_INT(int i) { FlagValue v; v.i = i; return v; } static FlagValue New_FLOAT(float f) { FlagValue v; v.f = f; return v; } static FlagValue New_STRING(const char* s) { FlagValue v; v.s = s; return v; } bool b; int i; double f; const char* s; };这个联合类型对命令行中键值对中的值进行封装,可以表示四种类型。
2.2 命令行中键值的表示 -- Flag
这个类是表示一对键值的抽象,包含下列元素:
class Flag { public: enum Type { BOOL, INT, FLOAT, STRING }; // Internal use only. Flag(const char* file, const char* name, const char* comment, Type type, void* variable, FlagValue default_); // General flag information const char* file() const { return file_; } const char* name() const { return name_; } const char* comment() const { return comment_; } // Flag type Type type() const { return type_; } // Flag variables bool* bool_variable() const { assert(type_ == BOOL); return &variable_->b; } int* int_variable() const { assert(type_ == INT); return &variable_->i; } double* float_variable() const { assert(type_ == FLOAT); return &variable_->f; } const char** string_variable() const { assert(type_ == STRING); return &variable_->s; } // Default values bool bool_default() const { assert(type_ == BOOL); return default_.b; } int int_default() const { assert(type_ == INT); return default_.i; } double float_default() const { assert(type_ == FLOAT); return default_.f; } const char* string_default() const { assert(type_ == STRING); return default_.s; } // Resets a flag to its default value void SetToDefault(); // Iteration support Flag* next() const { return next_; } // Prints flag information. The current flag value is only printed // if print_current_value is set. void Print(bool print_current_value); private: const char* file_; const char* name_; const char* comment_; Type type_; FlagValue* variable_; FlagValue default_; Flag* next_; friend class FlagList; // accesses next_ };
这个类维护一个全局的链表,链表中每一项都是命令行参数解析的结果,如:--prefix=/usr --localstatedir=/var/data 这就表示两个Flag对象,通过Flag对象的next域来关联。
这个类的属性和方法都是静态的,属性只有Flag* list_,用于维护命令行所有输入的参数所组成的链表。
主要方法如下:
SetFkagsFromCommandLine:解析根据命令行的输入,这里传入的是所有的命令行输入。
SplitArgument:解析命令行中具体的一个可以被解析的键值对。
class FlagList { public: FlagList(); static Flag* list() { return list_; } static void Print(const char* file, bool print_current_value); static Flag* Lookup(const char* name); static void SplitArgument(const char* arg, char* buffer, int buffer_size, const char** name, const char** value, bool* is_bool); static int SetFlagsFromCommandLine(int* argc, const char** argv); static inline int SetFlagsFromCommandLine(int* argc, char** argv) { return SetFlagsFromCommandLine(argc, const_cast<const char**>(argv)); } private: static Flag* list_; };
先看在链表中,查找指定的参数,这个实现比较简单。
Flag* FlagList::Lookup(const char* name) { Flag* f = list_; while (f != NULL && strcmp(name, f->name()) != 0) f = f->next(); return f; }
void FlagList::SplitArgument(const char* arg, char* buffer, int buffer_size, const char** name, const char** value, bool* is_bool) { *name = NULL; *value = NULL; *is_bool = false; if (*arg == '-') { // find the begin of the flag name arg++; // remove 1st '-' if (*arg == '-') arg++; // remove 2nd '-' if (arg[0] == 'n' && arg[1] == 'o') { arg += 2; // remove "no" *is_bool = true; } *name = arg; // find the end of the flag name while (*arg != '\0' && *arg != '=') arg++; // get the value if any if (*arg == '=') { // make a copy so we can NUL-terminate flag name int n = static_cast<int>(arg - *name); if (n >= buffer_size) Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size"); memcpy(buffer, *name, n * sizeof(char)); buffer[n] = '\0'; *name = buffer; // get the value *value = arg + 1; } } }上面的函数是对诸如--prefix=/usr 、--prefix、--prefix /usr之类的命令行进行解析,如果某项输入不以-开头,则不被解析。
流程如下:
int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv) { // parse arguments for (int i = 1; i < *argc; /* see below */) { int j = i; // j > 0 const char* arg = argv[i++]; // split arg into flag components char buffer[1024]; const char* name; const char* value; bool is_bool; SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool); if (name != NULL) { // lookup the flag Flag* flag = Lookup(name); if (flag == NULL) { fprintf(stderr, "Error: unrecognized flag %s\n", arg); return j; } // if we still need a flag value, use the next argument if available if (flag->type() != Flag::BOOL && value == NULL) { if (i < *argc) { value = argv[i++]; } else { fprintf(stderr, "Error: missing value for flag %s of type %s\n", arg, Type2String(flag->type())); return j; } } // set the flag char empty[] = { '\0' }; char* endp = empty; switch (flag->type()) { case Flag::BOOL: *flag->bool_variable() = !is_bool; break; case Flag::INT: *flag->int_variable() = strtol(value, &endp, 10); break; case Flag::FLOAT: *flag->float_variable() = strtod(value, &endp); break; case Flag::STRING: *flag->string_variable() = value; break; } // handle errors if ((flag->type() == Flag::BOOL && value != NULL) || (flag->type() != Flag::BOOL && is_bool) || *endp != '\0') { fprintf(stderr, "Error: illegal value for flag %s of type %s\n", arg, Type2String(flag->type())); return j; } } } return 0; }
源码下载地址:http://download.csdn.net/detail/zmxiangde_88/4789141
粘贴如下:
flags.h
#ifndef TALK_BASE_FLAGS_H__ #define TALK_BASE_FLAGS_H__ #include <assert.h> #include "talk/base/checks.h" #include "talk/base/common.h" // Internal use only. union FlagValue { // Note: Because in C++ non-bool values are silently converted into // bool values ('bool b = "false";' results in b == true!), we pass // and int argument to New_BOOL as this appears to be safer - sigh. // In particular, it prevents the (not uncommon!) bug where a bool // flag is defined via: DEFINE_bool(flag, "false", "some comment");. static FlagValue New_BOOL(int b) { FlagValue v; v.b = (b != 0); return v; } static FlagValue New_INT(int i) { FlagValue v; v.i = i; return v; } static FlagValue New_FLOAT(float f) { FlagValue v; v.f = f; return v; } static FlagValue New_STRING(const char* s) { FlagValue v; v.s = s; return v; } bool b; int i; double f; const char* s; }; // Each flag can be accessed programmatically via a Flag object. class Flag { public: enum Type { BOOL, INT, FLOAT, STRING }; // Internal use only. Flag(const char* file, const char* name, const char* comment, Type type, void* variable, FlagValue default_); // General flag information const char* file() const { return file_; } const char* name() const { return name_; } const char* comment() const { return comment_; } // Flag type Type type() const { return type_; } // Flag variables bool* bool_variable() const { assert(type_ == BOOL); return &variable_->b; } int* int_variable() const { assert(type_ == INT); return &variable_->i; } double* float_variable() const { assert(type_ == FLOAT); return &variable_->f; } const char** string_variable() const { assert(type_ == STRING); return &variable_->s; } // Default values bool bool_default() const { assert(type_ == BOOL); return default_.b; } int int_default() const { assert(type_ == INT); return default_.i; } double float_default() const { assert(type_ == FLOAT); return default_.f; } const char* string_default() const { assert(type_ == STRING); return default_.s; } // Resets a flag to its default value void SetToDefault(); // Iteration support Flag* next() const { return next_; } // Prints flag information. The current flag value is only printed // if print_current_value is set. void Print(bool print_current_value); private: const char* file_; const char* name_; const char* comment_; Type type_; FlagValue* variable_; FlagValue default_; Flag* next_; friend class FlagList; // accesses next_ }; // Internal use only. #define DEFINE_FLAG(type, c_type, name, default, comment) \ /* define and initialize the flag */ \ c_type FLAG_##name = (default); \ /* register the flag */ \ static Flag Flag_##name(__FILE__, #name, (comment), \ Flag::type, &FLAG_##name, \ FlagValue::New_##type(default)) // Internal use only. #define DECLARE_FLAG(c_type, name) \ /* declare the external flag */ \ extern c_type FLAG_##name // Use the following macros to define a new flag: #define DEFINE_bool(name, default, comment) \ DEFINE_FLAG(BOOL, bool, name, default, comment) #define DEFINE_int(name, default, comment) \ DEFINE_FLAG(INT, int, name, default, comment) #define DEFINE_float(name, default, comment) \ DEFINE_FLAG(FLOAT, double, name, default, comment) #define DEFINE_string(name, default, comment) \ DEFINE_FLAG(STRING, const char*, name, default, comment) // Use the following macros to declare a flag defined elsewhere: #define DECLARE_bool(name) DECLARE_FLAG(bool, name) #define DECLARE_int(name) DECLARE_FLAG(int, name) #define DECLARE_float(name) DECLARE_FLAG(double, name) #define DECLARE_string(name) DECLARE_FLAG(const char*, name) // The global list of all flags. class FlagList { public: FlagList(); // The NULL-terminated list of all flags. Traverse with Flag::next(). static Flag* list() { return list_; } // If file != NULL, prints information for all flags defined in file; // otherwise prints information for all flags in all files. The current // flag value is only printed if print_current_value is set. static void Print(const char* file, bool print_current_value); // Lookup a flag by name. Returns the matching flag or NULL. static Flag* Lookup(const char* name); // Helper function to parse flags: Takes an argument arg and splits it into // a flag name and flag value (or NULL if they are missing). is_bool is set // if the arg started with "-no" or "--no". The buffer may be used to NUL- // terminate the name, it must be large enough to hold any possible name. static void SplitArgument(const char* arg, char* buffer, int buffer_size, const char** name, const char** value, bool* is_bool); // Set the flag values by parsing the command line. If remove_flags // is set, the flags and associated values are removed from (argc, // argv). Returns 0 if no error occurred. Otherwise, returns the // argv index > 0 for the argument where an error occurred. In that // case, (argc, argv) will remain unchanged indepdendent of the // remove_flags value, and no assumptions about flag settings should // be made. // // The following syntax for flags is accepted (both '-' and '--' are ok): // // --flag (bool flags only) // --noflag (bool flags only) // --flag=value (non-bool flags only, no spaces around '=') // --flag value (non-bool flags only) static int SetFlagsFromCommandLine(int* argc, const char** argv, bool remove_flags); static inline int SetFlagsFromCommandLine(int* argc, char** argv, bool remove_flags) { return SetFlagsFromCommandLine(argc, const_cast<const char**>(argv), remove_flags); } // Registers a new flag. Called during program initialization. Not // thread-safe. static void Register(Flag* flag); private: static Flag* list_; }; #ifdef WIN32 // A helper class to translate Windows command line arguments into UTF8, // which then allows us to just pass them to the flags system. // This encapsulates all the work of getting the command line and translating // it to an array of 8-bit strings; all you have to do is create one of these, // and then call argc() and argv(). class WindowsCommandLineArguments { public: WindowsCommandLineArguments(); ~WindowsCommandLineArguments(); int argc() { return argc_; } char **argv() { return argv_; } private: int argc_; char **argv_; private: DISALLOW_EVIL_CONSTRUCTORS(WindowsCommandLineArguments); }; #endif // WIN32 #endif // SHARED_COMMANDLINEFLAGS_FLAGS_H__
#include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef WIN32 #include "talk/base/win32.h" #include <shellapi.h> #endif #include "talk/base/flags.h" // ----------------------------------------------------------------------------- // Implementation of Flag Flag::Flag(const char* file, const char* name, const char* comment, Type type, void* variable, FlagValue default__) : file_(file), name_(name), comment_(comment), type_(type), variable_(reinterpret_cast<FlagValue*>(variable)), default_(default__) { FlagList::Register(this); } void Flag::SetToDefault() { // Note that we cannot simply do '*variable_ = default_;' since // flag variables are not really of type FlagValue and thus may // be smaller! The FlagValue union is simply 'overlayed' on top // of a flag variable for convenient access. Since union members // are guarantee to be aligned at the beginning, this works. switch (type_) { case Flag::BOOL: variable_->b = default_.b; return; case Flag::INT: variable_->i = default_.i; return; case Flag::FLOAT: variable_->f = default_.f; return; case Flag::STRING: variable_->s = default_.s; return; } UNREACHABLE(); } static const char* Type2String(Flag::Type type) { switch (type) { case Flag::BOOL: return "bool"; case Flag::INT: return "int"; case Flag::FLOAT: return "float"; case Flag::STRING: return "string"; } UNREACHABLE(); return NULL; } static void PrintFlagValue(Flag::Type type, FlagValue* p) { switch (type) { case Flag::BOOL: printf("%s", (p->b ? "true" : "false")); return; case Flag::INT: printf("%d", p->i); return; case Flag::FLOAT: printf("%f", p->f); return; case Flag::STRING: printf("%s", p->s); return; } UNREACHABLE(); } void Flag::Print(bool print_current_value) { printf(" --%s (%s) type: %s default: ", name_, comment_, Type2String(type_)); PrintFlagValue(type_, &default_); if (print_current_value) { printf(" current value: "); PrintFlagValue(type_, variable_); } printf("\n"); } // ----------------------------------------------------------------------------- // Implementation of FlagList Flag* FlagList::list_ = NULL; FlagList::FlagList() { list_ = NULL; } void FlagList::Print(const char* file, bool print_current_value) { // Since flag registration is likely by file (= C++ file), // we don't need to sort by file and still get grouped output. const char* current = NULL; for (Flag* f = list_; f != NULL; f = f->next()) { if (file == NULL || file == f->file()) { if (current != f->file()) { printf("Flags from %s:\n", f->file()); current = f->file(); } f->Print(print_current_value); } } } Flag* FlagList::Lookup(const char* name) { Flag* f = list_; while (f != NULL && strcmp(name, f->name()) != 0) f = f->next(); return f; } void FlagList::SplitArgument(const char* arg, char* buffer, int buffer_size, const char** name, const char** value, bool* is_bool) { *name = NULL; *value = NULL; *is_bool = false; if (*arg == '-') { // find the begin of the flag name arg++; // remove 1st '-' if (*arg == '-') arg++; // remove 2nd '-' if (arg[0] == 'n' && arg[1] == 'o') { arg += 2; // remove "no" *is_bool = true; } *name = arg; // find the end of the flag name while (*arg != '\0' && *arg != '=') arg++; // get the value if any if (*arg == '=') { // make a copy so we can NUL-terminate flag name int n = static_cast<int>(arg - *name); if (n >= buffer_size) Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size"); memcpy(buffer, *name, n * sizeof(char)); buffer[n] = '\0'; *name = buffer; // get the value *value = arg + 1; } } } int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv, bool remove_flags) { // parse arguments for (int i = 1; i < *argc; /* see below */) { int j = i; // j > 0 const char* arg = argv[i++]; // split arg into flag components char buffer[1024]; const char* name; const char* value; bool is_bool; SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool); if (name != NULL) { // lookup the flag Flag* flag = Lookup(name); if (flag == NULL) { fprintf(stderr, "Error: unrecognized flag %s\n", arg); return j; } // if we still need a flag value, use the next argument if available if (flag->type() != Flag::BOOL && value == NULL) { if (i < *argc) { value = argv[i++]; } else { fprintf(stderr, "Error: missing value for flag %s of type %s\n", arg, Type2String(flag->type())); return j; } } // set the flag char empty[] = { '\0' }; char* endp = empty; switch (flag->type()) { case Flag::BOOL: *flag->bool_variable() = !is_bool; break; case Flag::INT: *flag->int_variable() = strtol(value, &endp, 10); break; case Flag::FLOAT: *flag->float_variable() = strtod(value, &endp); break; case Flag::STRING: *flag->string_variable() = value; break; } // handle errors if ((flag->type() == Flag::BOOL && value != NULL) || (flag->type() != Flag::BOOL && is_bool) || *endp != '\0') { fprintf(stderr, "Error: illegal value for flag %s of type %s\n", arg, Type2String(flag->type())); return j; } // remove the flag & value from the command if (remove_flags) while (j < i) argv[j++] = NULL; } } // shrink the argument list if (remove_flags) { int j = 1; for (int i = 1; i < *argc; i++) { if (argv[i] != NULL) argv[j++] = argv[i]; } *argc = j; } // parsed all flags successfully return 0; } void FlagList::Register(Flag* flag) { assert(flag != NULL && strlen(flag->name()) > 0); if (Lookup(flag->name()) != NULL) Fatal(flag->file(), 0, "flag %s declared twice", flag->name()); flag->next_ = list_; list_ = flag; } #ifdef WIN32 WindowsCommandLineArguments::WindowsCommandLineArguments() { // start by getting the command line. LPTSTR command_line = ::GetCommandLine(); // now, convert it to a list of wide char strings. LPWSTR *wide_argv = ::CommandLineToArgvW(command_line, &argc_); // now allocate an array big enough to hold that many string pointers. argv_ = new char*[argc_]; // iterate over the returned wide strings; for(int i = 0; i < argc_; ++i) { std::string s = talk_base::ToUtf8(wide_argv[i], wcslen(wide_argv[i])); char *buffer = new char[s.length() + 1]; talk_base::strcpyn(buffer, s.length() + 1, s.c_str()); // make sure the argv array has the right string at this point. argv_[i] = buffer; } LocalFree(wide_argv); } WindowsCommandLineArguments::~WindowsCommandLineArguments() { // need to free each string in the array, and then the array. for(int i = 0; i < argc_; i++) { delete[] argv_[i]; } delete[] argv_; } #endif // WIN32