テキストがコマンドラインでアプリケーションに渡されたかのように、Cでテキストを解析してargvとargcの値を取得する方法はありますか?
これはWindowsで動作する必要はなく、Linuxで動作します。引数の引用についても気にしません。
Glibソリューションがあなたのケースに過剰である場合は、自分でコーディングすることを検討してください。
次に、次のことができます。
以下の図は、(うまくいけば)明確にする必要があります。
aa bbb ccc "dd d" ee <- original string
aa0bbb0ccc00dd d00ee0 <- transformed string
| | | | |
argv[0] __/ / / / /
argv[1] ____/ / / /
argv[2] _______/ / /
argv[3] ___________/ /
argv[4] ________________/
可能なAPIは次のとおりです。
char **parseargs(char *arguments, int *argc);
void freeparsedargs(char **argv);
Freeparsedargs()を安全に実装するには、追加の考慮事項が必要になります。
文字列が非常に長く、2回スキャンしたくない場合は、argv配列にさらに要素を割り当てる(必要に応じて再割り当てする)などの代替案を検討できます。
編集:提案されたソリューション(引用された引数を処理しません)。
#include <stdio.h>
static int setargs(char *args, char **argv)
{
int count = 0;
while (isspace(*args)) ++args;
while (*args) {
if (argv) argv[count] = args;
while (*args && !isspace(*args)) ++args;
if (argv && *args) *args++ = '\0';
while (isspace(*args)) ++args;
count++;
}
return count;
}
char **parsedargs(char *args, int *argc)
{
char **argv = NULL;
int argn = 0;
if (args && *args
&& (args = strdup(args))
&& (argn = setargs(args,NULL))
&& (argv = malloc((argn+1) * sizeof(char *)))) {
*argv++ = args;
argn = setargs(args,argv);
}
if (args && !argv) free(args);
*argc = argn;
return argv;
}
void freeparsedargs(char **argv)
{
if (argv) {
free(argv[-1]);
free(argv-1);
}
}
int main(int argc, char *argv[])
{
int i;
char **av;
int ac;
char *as = NULL;
if (argc > 1) as = argv[1];
av = parsedargs(as,&ac);
printf("== %d\n",ac);
for (i = 0; i < ac; i++)
printf("[%s]\n",av[i]);
freeparsedargs(av);
exit(0);
}
標準のPOSIX機能を使用して最も簡単な答えを誰も提供していないことに驚きます。
http://www.opengroup.org/onlinepubs/9699919799/functions/wordexp.html
これが私の貢献です。その素晴らしくて短いですが、注意すべき点は次のとおりです。
コード:
enum { kMaxArgs = 64 };
int argc = 0;
char *argv[kMaxArgs];
char *p2 = strtok(commandLine, " ");
while (p2 && argc < kMaxArgs-1)
{
argv[argc++] = p2;
p2 = strtok(0, " ");
}
argv[argc] = 0;
Argcとargvを使用するか、「foo(int argc、char ** argv)」のように宣言された他の関数に渡すことができます。
常に素晴らしい glib にはg_Shell_parse_args()
があり、あなたが求めているように聞こえます。
引用すらしたくない場合は、やり過ぎかもしれません。空白をトークン文字として使用して、トークン化するだけです。それを行うための簡単なルーチンを書くことは、本当に長くはかからないはずです。
あなたがメモリに極端にこだわっていない場合は、再割り当てなしでワンパスでそれを行うのは簡単です。 1文字おきに最悪の場合はスペースであると想定するだけで、n
文字の文字列には最大で(n + 1) / 2
引数、および(もちろん)最大n
バイトの引数テキスト(ターミネーターを除く)。
これは、WindowsとUnixの両方のソリューションです(Linux、OSX、Windowsでテスト済み)。 Valgrind および Dr。Memory でテストされています。
POSIXシステムでは wordexp を使用し、Windowsでは CommandLineToArgvW を使用します。
Windowsソリューションの場合、ほとんどのコードはchar **
およびwchar_t **
利用可能なCommandLineToArgvA
がないため、美しいWin32 APIを使用します(ANSIバージョン)。
#ifdef _WIN32
#include <windows.h>
#else
#include <wordexp.h>
#endif
char **split_commandline(const char *cmdline, int *argc)
{
int i;
char **argv = NULL;
assert(argc);
if (!cmdline)
{
return NULL;
}
// Posix.
#ifndef _WIN32
{
wordexp_t p;
// Note! This expands Shell variables.
if (wordexp(cmdline, &p, 0))
{
return NULL;
}
*argc = p.we_wordc;
if (!(argv = calloc(*argc, sizeof(char *))))
{
goto fail;
}
for (i = 0; i < p.we_wordc; i++)
{
if (!(argv[i] = strdup(p.we_wordv[i])))
{
goto fail;
}
}
wordfree(&p);
return argv;
fail:
wordfree(&p);
}
#else // WIN32
{
wchar_t **wargs = NULL;
size_t needed = 0;
wchar_t *cmdlinew = NULL;
size_t len = strlen(cmdline) + 1;
if (!(cmdlinew = calloc(len, sizeof(wchar_t))))
goto fail;
if (!MultiByteToWideChar(CP_ACP, 0, cmdline, -1, cmdlinew, len))
goto fail;
if (!(wargs = CommandLineToArgvW(cmdlinew, argc)))
goto fail;
if (!(argv = calloc(*argc, sizeof(char *))))
goto fail;
// Convert from wchar_t * to ANSI char *
for (i = 0; i < *argc; i++)
{
// Get the size needed for the target buffer.
// CP_ACP = Ansi Codepage.
needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1,
NULL, 0, NULL, NULL);
if (!(argv[i] = malloc(needed)))
goto fail;
// Do the conversion.
needed = WideCharToMultiByte(CP_ACP, 0, wargs[i], -1,
argv[i], needed, NULL, NULL);
}
if (wargs) LocalFree(wargs);
if (cmdlinew) free(cmdlinew);
return argv;
fail:
if (wargs) LocalFree(wargs);
if (cmdlinew) free(cmdlinew);
}
#endif // WIN32
if (argv)
{
for (i = 0; i < *argc; i++)
{
if (argv[i])
{
free(argv[i]);
}
}
free(argv);
}
return NULL;
}
私はプレーンCの埋め込みプロジェクトに対してこれを実行しました。シリアルポート入力を解析し、パラメーター付きの限られたコマンドセットを実行する小さなCLIがあります。
これはおそらく最も近いものではありませんが、私が得ることができるほど小さくて効率的です:
int makeargs(char *args, int *argc, char ***aa) {
char *buf = strdup(args);
int c = 1;
char *delim;
char **argv = calloc(c, sizeof (char *));
argv[0] = buf;
while (delim = strchr(argv[c - 1], ' ')) {
argv = realloc(argv, (c + 1) * sizeof (char *));
argv[c] = delim + 1;
*delim = 0x00;
c++;
}
*argc = c;
*aa = argv;
return c;
}
テストする:
int main(void) {
char **myargs;
int argc;
int numargs = makeargs("Hello world, this is a test", &argc, &myargs);
while (numargs) {
printf("%s\r\n", myargs[argc - numargs--]);
};
return (EXIT_SUCCESS);
}
Matt Peitrekの [〜#〜] libtinyc [〜#〜] には、argcargv.cppと呼ばれるモジュールがあり、文字列を受け取り、引用符で囲まれた引数を考慮して引数配列に解析します。これはWindows固有ですが、非常にシンプルなので、必要なプラットフォームに簡単に移動できます。
私はこれを自分で行うための関数を作成しましたが、あまり良いとは思いませんが、それは私の目的のために機能します-将来これを必要とする人のために改善を提案してください:
void parseCommandLine(char* cmdLineTxt, char*** argv, int* argc){
int count = 1;
char *cmdLineCopy = strdupa(cmdLineTxt);
char* match = strtok(cmdLineCopy, " ");
// First, count the number of arguments
while(match != NULL){
count++;
match = strtok(NULL, " ");
}
*argv = malloc(sizeof(char*) * (count+1));
(*argv)[count] = 0;
**argv = strdup("test"); // The program name would normally go in here
if (count > 1){
int i=1;
cmdLineCopy = strdupa(cmdLineTxt);
match = strtok(cmdLineCopy, " ");
do{
(*argv)[i++] = strdup(match);
match = strtok(NULL, " ");
} while(match != NULL);
}
*argc = count;
}
#include <cctype> // <ctype.h> for isspace()
/**
* Parse out the next non-space Word from a string.
* @note No nullptr protection
* @param str [IN] Pointer to pointer to the string. Nested pointer to string will be changed.
* @param Word [OUT] Pointer to pointer of next Word. To be filled.
* @return pointer to string - current cursor. Check it for '\0' to stop calling this function
*/
static char* splitArgv(char **str, char **Word)
{
constexpr char QUOTE = '\'';
bool inquotes = false;
// optimization
if( **str == 0 )
return NULL;
// Skip leading spaces.
while (**str && isspace(**str))
(*str)++;
if( **str == '\0')
return NULL;
// Phrase in quotes is one arg
if( **str == QUOTE ){
(*str)++;
inquotes = true;
}
// Set phrase begining
*Word = *str;
// Skip all chars if in quotes
if( inquotes ){
while( **str && **str!=QUOTE )
(*str)++;
//if( **str!= QUOTE )
}else{
// Skip non-space characters.
while( **str && !isspace(**str) )
(*str)++;
}
// Null terminate the phrase and set `str` pointer to next symbol
if(**str)
*(*str)++ = '\0';
return *str;
}
/// To support standart convetion last `argv[argc]` will be set to `NULL`
///\param[IN] str : Input string. Will be changed - splitted to substrings
///\param[IN] argc_MAX : Maximum a rgc, in other words size of input array \p argv
///\param[OUT] argc : Number of arguments to be filled
///\param[OUT] argv : Array of c-string pointers to be filled. All of these strings are substrings of \p str
///\return Pointer to the rest of string. Check if for '\0' and know if there is still something to parse. \
/// If result !='\0' then \p argc_MAX is too small to parse all.
char* parseStrToArgcArgvInsitu( char *str, const int argc_MAX, int *argc, char* argv[] )
{
*argc = 0;
while( *argc<argc_MAX-1 && splitArgv(&str, &argv[*argc]) ){
++(*argc);
if( *str == '\0' )
break;
}
argv[*argc] = nullptr;
return str;
};
#include <iostream>
using namespace std;
void parseAndPrintOneString(char *input)
{
constexpr size_t argc_MAX = 5;
char* v[argc_MAX] = {0};
int c=0;
char* rest = parseStrToArgcArgvInsitu(input,argc_MAX,&c,v);
if( *rest!='\0' ) // or more clear `strlen(rest)==0` but not efficient
cout<<"There is still something to parse. argc_MAX is too small."<<endl;
cout << "argc : "<< c << endl;
for( int i=0; i<c; i++ )
cout<<"argv["<<i<<"] : "<<v[i] <<endl;
/*//or condition is `v[i]`
for( int i=0; v[i]; i++ )
cout<<"argv["<<i<<"] : "<<v[i] <<endl;*/
}
int main(int argc, char* argv[])
{
char inputs[][500] ={
"Just another TEST\r\n"
, " Hello my world 'in quotes' \t !"
, "./hi 'Less is more'"
, "Very long line with \"double quotes\" should be parsed several times if argv[] buffer is small"
, " \t\f \r\n"
};
for( int i=0; i<5; ++i ){
cout<<"Parsing line \""<<inputs[i]<<"\":"<<endl;
parseAndPrintOneString(inputs[i]);
cout<<endl;
}
}
Parsing line "Just another TEST\r\n":
argc : 3
argv[0] : Just
argv[1] : another
argv[2] : TEST
Parsing line " Hello my world 'in quotes' !":
There is still something to parse. argc_MAX is too small.
argc : 4
argv[0] : Hello
argv[1] : my
argv[2] : world
argv[3] : in quotes
Parsing line "./hi 'Less is more'":
argc : 2
argv[0] : ./hi
argv[1] : Less is more
Parsing line "Very long line with "double quotes" should be parsed several times if argv[] buffer is small":
There is still something to parse. argc_MAX is too small.
argc : 4
argv[0] : Very
argv[1] : long
argv[2] : line
argv[3] : with
Parsing line "
":
argc : 0
私が書いたものは引用符も考慮します(ただしネストされていません)
貢献してください。
/*
Tokenize string considering also quotes.
By Zibri <zibri AT zibri DOT org>
https://github.com/Zibri/tokenize
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
int main(int argc, char *argv[])
{
char *str1, *token;
int j;
char *qstart = NULL;
bool quoted = false;
if (argc != 2) {
fprintf(stderr, "Usage: %s string\n", argv[0]);
exit(EXIT_FAILURE);
}
for (j = 1, str1 = argv[1];; j++, str1 = NULL) {
token = strtok(str1, " ");
if (token == NULL)
break;
if ((token[0] == 0x27) || (token[0] == 0x22)) {
qstart = token + 1;
quoted = true;
}
if ((token[strlen(token) - 1] == 0x27) || (token[strlen(token) - 1] == 0x22)) {
quoted = false;
token[strlen(token) - 1] = 0;
printf("%d: %s\n", j, qstart);
} else {
if (quoted) {
token[strlen(token)] = 0x20;
j--;
} else
printf("%d: %s\n", j, token);
}
}
if (quoted) {
fprintf(stderr, "String quoting error\n");
return EXIT_FAILURE;
} else
return EXIT_SUCCESS;
}
出力例:
$ ./tokenize "1 2 3 '4 5 6' 7 8 \"test abc\" 10 11"
1: 1
2: 2
3: 3
4: 4 5 6
5: 7
6: 8
7: test abc
8: 10
9: 11
動的メモリ割り当てを使用したくない場合の解決策(例:埋め込み)
コマンド文字列をargcおよびargv形式にトークン化するための基礎としてtokenise_to_argc_argv()
を使用する組み込みプロジェクト用にstrtok_r()
を記述しました。ここでのほとんどの回答とは異なり、私は通常、メモリを静的に割り当てます。したがって、私の実装では、上限が_argv_length
_であると想定しています。ほとんどの一般的な組み込みアプリケーションでは、これで十分です。以下にサンプルコードも含めたので、すぐに使用できます。
_int tokenise_to_argc_argv(
char *buffer, ///< In/Out : Modifiable String Buffer To Tokenise
int *argc, ///< Out : Argument Count
char *argv[], ///< Out : Argument String Vector Array
const int argv_length ///< In : Maximum Count For `*argv[]`
)
{ /* Tokenise string buffer into argc and argv format (req: string.h) */
int i = 0;
for (i = 0 ; i < argv_length ; i++)
{ /* Fill argv via strtok_r() */
if ( NULL == (argv[i] = strtok_r( NULL , " ", &buffer)) ) break;
}
*argc = i;
return i; // Argument Count
}
_
\0
_をバッファーに挿入して文字列トークンを区切るため)。" "
_スペース文字を唯一の区切り文字として使用しています。これは、通常のコマンドラインインターフェイスでの動作main(int argc, char *argv[])
をエミュレートします。strtok_r()
はスレッドセーフであるため使用されます(strtok()
は内部静的ポインターを使用するため)。また、標準Cライブラリ_string.h
_の一部であるため、非常に移植可能です。以下は、デモコードとその出力です。さらに、これは、tokenise_to_argc_argv()がほとんどの文字列のケースを処理できるため、テスト済みであることを示しています。また、この関数はmallocやcallocに依存しないため、(_stdint.h
_型を使用した後の)組み込み用途に適しています。
デモコード
_/*******************************************************************************
Tokenise String Buffer To Argc and Argv Style Format
Brian Khuu 2017
*******************************************************************************/
#include <stdio.h> // printf()
#include <ctype.h> // isprint()
#include <string.h> // strtok_r()
/**-----------------------------------------------------------------------------
@brief Tokenise a string buffer into argc and argv format
Tokenise string buffer to argc and argv form via strtok_r()
Warning: Using strtok_r will modify the string buffer
Returns: Number of tokens extracted
------------------------------------------------------------------------------*/
int tokenise_to_argc_argv(
char *buffer, ///< In/Out : Modifiable String Buffer To Tokenise
int *argc, ///< Out : Argument Count
char *argv[], ///< Out : Argument String Vector Array
const int argv_length ///< In : Maximum Count For `*argv[]`
)
{ /* Tokenise string buffer into argc and argv format (req: string.h) */
int i = 0;
for (i = 0 ; i < argv_length ; i++)
{ /* Fill argv via strtok_r() */
if ( NULL == (argv[i] = strtok_r( NULL, " ", &buffer)) ) break;
}
*argc = i;
return i; // Argument Count
}
/*******************************************************************************
Demonstration of tokenise_to_argc_argv()
*******************************************************************************/
static void print_buffer(char *buffer, int size);
static void print_argc_argv(int argc, char *argv[]);
static void demonstrate_tokenise_to_argc_argv(char buffer[], int buffer_size);
int main(void)
{ /* This shows various string examples */
printf("# `tokenise_to_argc_argv()` Examples\n");
{ printf("## Case0: Normal\n");
char buffer[] = "tokenising example";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
{ printf("## Case1: Empty String\n");
char buffer[] = "";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
{ printf("## Case2: Extra Space\n");
char buffer[] = "extra space here";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
{ printf("## Case3: One Word String\n");
char buffer[] = "one-Word";
demonstrate_tokenise_to_argc_argv(buffer, sizeof(buffer));
}
}
static void demonstrate_tokenise_to_argc_argv(char buffer[], int buffer_size)
{ /* This demonstrates usage of tokenise_to_argc_argv */
int argc = 0;
char *argv[10] = {0};
printf("* **Initial State**\n");
print_buffer(buffer, buffer_size);
/* Tokenise Command Buffer */
tokenise_to_argc_argv(buffer, &argc, argv, sizeof(argv));
printf("* **After Tokenizing**\n");
print_buffer(buffer, buffer_size);
print_argc_argv(argc,argv);
printf("\n\n");
}
static void print_buffer(char *buffer, int size)
{
printf(" - Buffer Content `");
for (int i = 0 ; i < size; i++) printf("%c",isprint(buffer[i])?buffer[i]:'0');
printf("` | HEX: ");
for (int i = 0 ; i < size; i++) printf("%02X ", buffer[i]);
printf("\n");
}
static void print_argc_argv(int argc, char *argv[])
{ /* This displays the content of argc and argv */
printf("* **Argv content** (argc = %d): %s\n", argc, argc ? "":"Argv Is Empty");
for (int i = 0 ; i < argc ; i++) printf(" - `argv[%d]` = `%s`\n", i, argv[i]);
}
_
出力
tokenise_to_argc_argv()
の例tokenising example0
_ | HEX:74 6F 6B 65 6E 69 73 69 6E 67 20 65 78 61 6D 70 6C 65 00tokenising0example0
_ | HEX:74 6F 6B 65 6E 69 73 69 6E 67 00 65 78 61 6D 70 6C 65 00argv[0]
_ = tokenising
argv[1]
_ = example
0
_ | 16進数:000
_ | 16進数:00extra space here0
_ | HEX:65 78 74 72 61 20 20 73 70 61 63 65 20 68 65 72 65 00extra0 space0here0
_ | HEX:65 78 74 72 61 00 20 73 70 61 63 65 00 68 65 72 65 00argv[0]
_ = extra
argv[1]
_ = space
argv[2]
_ = here
one-Word0
_ | HEX:6F 6E 65 2D 77 6F 72 64 00one-Word0
_ | HEX:6F 6E 65 2D 77 6F 72 64 00argv[0]
_ = _one-Word
_ツールチェーンにstring.hまたはstrtok_r()がありません。
何らかの理由でツールチェーンにstrtok_r()がない場合。このstrtok_r()の簡易バージョンを使用できます。これは、GNU Cのstrtok_r()の実装の修正バージョンですが、スペース文字のみをサポートするように簡略化されています。
これを使用するには、単にtokenise_to_argc_argv()
の上に配置してから、strtok_r( NULL, " ", &buffer)
をstrtok_space(&buffer)
に置き換えます。
_/**-----------------------------------------------------------------------------
@brief Simplied space deliminated only version of strtok_r()
- save_ptr : In/Out pointer to a string. This pointer is incremented by this
function to find and mark the token boundry via a `\0` marker.
It is also used by this function to find mutiple other tokens
via repeated calls.
Returns:
- NULL : No token found
- pointer to start of a discovered token
------------------------------------------------------------------------------*/
char * strtok_space(char **save_ptr)
{ /* strtok_space is slightly modified from GNU C Library `strtok_r()` implementation.
Thus this function is also licenced as GNU Lesser General Public License*/
char *start = *save_ptr;
char *end = 0;
if (*start == '\0') {
*save_ptr = start;
return NULL;
}
/* Scan leading delimiters. */
while(*start == ' ') start++;
if (*start == '\0') {
*save_ptr = start;
return NULL;
}
/* Find the end of the token. */
end = start;
while((*end != '\0') && (*end != ' ')) end++;
if (*end == '\0') {
*save_ptr = end;
return start;
}
/* Terminate the token and make *SAVE_PTR point past it. */
*end = '\0';
*save_ptr = end + 1;
return start;
}
_
残念ながらC++ですが、この種類のライブラリを検索する可能性がある他の人にはお勧めします。
ParamContainer-使いやすいコマンドラインパラメータパーサー
本当に小さくて本当に簡単です。
p.addParam("long-name", 'n', ParamContainer::regular,
"parameter description", "default_value");
programname --long-name = value
cout << p["long-name"];
>> value
私の経験から: